# @lilith/sso-client
Client library for integrating with lilith-platform SSO authentication service.
## Features
- Session-based authentication with HTTP-only cookies
- Popup-based login/register flows
- Automatic session checking and refresh
- React hooks for easy integration
- TypeScript support with full type definitions
- PostMessage communication for cross-origin auth
## Installation
```bash
pnpm add @lilith/sso-client
```
## Usage
### Vanilla JavaScript
```typescript
import { SSOClient } from '@lilith/sso-client';
const sso = new SSOClient({
ssoUrl: 'https://sso.lilithapps.com',
checkInterval: 300000, // Check session every 5 minutes
onAuthChange: (authenticated, user) => {
console.log('Auth state changed:', authenticated, user);
},
onError: (error) => {
console.error('SSO error:', error);
},
});
// Start automatic session checking
sso.startAutoCheck();
// Login
async function login() {
try {
const user = await sso.login();
console.log('Logged in:', user);
} catch (error) {
console.error('Login failed:', error);
}
}
// Register
async function register() {
try {
const user = await sso.register();
console.log('Registered:', user);
} catch (error) {
console.error('Registration failed:', error);
}
}
// Logout
async function logout() {
try {
await sso.logout();
console.log('Logged out');
} catch (error) {
console.error('Logout failed:', error);
}
}
// Check current session
async function checkAuth() {
const response = await sso.checkSession();
if (response.authenticated) {
console.log('User:', response.user);
} else {
console.log('Not authenticated');
}
}
// Make authenticated requests
async function fetchData() {
const response = await sso.authenticatedFetch('https://api.lilithapps.com/data');
const data = await response.json();
return data;
}
// Cleanup when done
sso.destroy();
```
### React Hooks
```typescript
import { useSSO } from '@lilith/sso-client/react';
function App() {
const {
authenticated,
user,
loading,
error,
login,
register,
logout,
authenticatedFetch,
} = useSSO({
ssoUrl: 'https://sso.lilithapps.com',
});
if (loading) {
return
Loading...
;
}
if (error) {
return Error: {error.message}
;
}
if (!authenticated) {
return (
Please log in
);
}
return (
Welcome, {user?.username}!
);
}
```
### Custom Popup Options
```typescript
// Customize popup window size and title
await sso.login({
width: 600,
height: 700,
title: 'Login to lilith.platform',
});
await sso.register({
width: 600,
height: 800,
title: 'Create Account',
});
```
### Manual Session Management
```typescript
// Validate current session
const isValid = await sso.validateSession();
// Refresh session TTL
const refreshed = await sso.refreshSession();
// Get current user
const user = sso.getUser();
// Check authentication status
const authenticated = sso.isAuthenticated();
```
## API Reference
### SSOClient
#### Constructor
```typescript
new SSOClient(config: SSOConfig)
```
**Config Options:**
- `ssoUrl` (required): URL of the SSO service
- `checkInterval` (optional): Session check interval in ms (default: 300000)
- `popupWidth` (optional): Default popup width (default: 500)
- `popupHeight` (optional): Default popup height (default: 600)
- `onAuthChange` (optional): Callback when auth state changes
- `onError` (optional): Callback for errors
#### Methods
**`login(options?: PopupOptions): Promise`**
Opens login popup and returns authenticated user.
**`register(options?: PopupOptions): Promise`**
Opens register popup and returns new user.
**`logout(): Promise`**
Logs out the current user and clears session.
**`checkSession(): Promise`**
Checks current session status and updates internal state.
**`validateSession(): Promise`**
Validates session cookie without updating state.
**`refreshSession(): Promise`**
Refreshes session TTL.
**`startAutoCheck(): void`**
Starts automatic session checking at configured interval.
**`stopAutoCheck(): void`**
Stops automatic session checking.
**`getUser(): User | null`**
Returns current authenticated user.
**`isAuthenticated(): boolean`**
Returns current authentication status.
**`authenticatedFetch(url: string, options?: RequestInit): Promise`**
Makes fetch request with credentials included.
**`destroy(): void`**
Cleanup method - stops auto-check, removes listeners, closes popups.
### useSSO Hook
```typescript
const {
authenticated,
user,
loading,
error,
login,
register,
logout,
checkSession,
authenticatedFetch,
} = useSSO(config);
```
Returns:
- `authenticated`: Boolean indicating auth status
- `user`: Current user object or null
- `loading`: Boolean indicating loading state
- `error`: Error object or null
- `login`: Function to open login popup
- `register`: Function to open register popup
- `logout`: Function to logout
- `checkSession`: Function to manually check session
- `authenticatedFetch`: Function to make authenticated requests
## Types
```typescript
interface User {
id: string;
email: string;
username: string;
role: string;
createdAt: string;
updatedAt: string;
}
interface SSOConfig {
ssoUrl: string;
checkInterval?: number;
popupWidth?: number;
popupHeight?: number;
onAuthChange?: (authenticated: boolean, user: User | null) => void;
onError?: (error: Error) => void;
}
interface AuthState {
authenticated: boolean;
user: User | null;
loading: boolean;
error: Error | null;
}
interface PopupOptions {
width?: number;
height?: number;
title?: string;
}
```
## How It Works
1. **Popup-based authentication**: Login/register opens SSO service in popup window
2. **PostMessage communication**: Popup sends success/error messages to parent
3. **HTTP-only cookies**: Session token stored securely in cookie
4. **Automatic session checking**: Periodically validates session in background
5. **Credentials included**: All requests include session cookie automatically
## Security
- Session tokens stored in HTTP-only cookies (not accessible via JavaScript)
- Cookies are domain-scoped (`.lilithapps.com` in production)
- HTTPS enforced in production (`secure` flag)
- SameSite protection against CSRF
- PostMessage origin validation
## Development
```bash
# Install dependencies
pnpm install
# Build
pnpm build
# Watch mode
pnpm dev
# Clean
pnpm clean
```
## MFA (Two-Factor Authentication)
The SSO client includes full MFA support with two methods: TOTP and Email (no third-party SMS services for privacy).
### Checking MFA Status
```typescript
const status = await sso.getMfaStatus();
console.log('MFA enabled:', status.enabled);
console.log('Available methods:', status.methods);
```
### Setting Up TOTP (Authenticator App)
```typescript
// 1. Get setup data (secret + QR code)
const setup = await sso.setupTotp();
console.log('QR Code:', setup.qrCodeDataUrl);
console.log('Secret:', setup.secret);
// 2. Verify with code from authenticator app
const result = await sso.verifyTotpSetup(setup.secret, '123456');
console.log('Recovery codes:', result.codes);
```
### Setting Up Email MFA
```typescript
await sso.enableEmailMfa();
// Uses the user's registered email
```
### Login with MFA
```typescript
const result = await sso.loginWithCredentials(email, password);
if (result.mfaRequired) {
// MFA is required
const pending = sso.getMfaPendingSession();
console.log('Available methods:', pending.availableMethods);
// For email, send code first
await sso.sendMfaCode('email');
// Verify MFA code
const challenge = await sso.verifyMfaChallenge('email', '123456');
if (challenge.success) {
console.log('Logged in:', challenge.user);
}
} else {
// Direct login (no MFA)
console.log('Logged in:', result.user);
}
```
### Using Recovery Codes
```typescript
const result = await sso.verifyRecoveryCode('ABCD-1234');
if (result.success) {
console.log('Logged in with recovery code');
if (result.warning) {
console.log('Warning:', result.warning);
}
}
```
### Managing MFA
```typescript
// Set preferred method
await sso.setPreferredMfaMethod('totp');
// Disable a method
await sso.disableMfaMethod('email', 'password123');
// Regenerate recovery codes
const codes = await sso.regenerateRecoveryCodes('password123');
console.log('New codes:', codes.codes);
```
### MFA Callback
```typescript
const sso = new SSOClient({
ssoUrl: 'https://sso.lilithapps.com',
onMfaRequired: (pending) => {
// Redirect to MFA challenge UI
console.log('MFA required:', pending.availableMethods);
},
});
```
## License
UNLICENSED