platform-codebase/@packages/@infrastructure/sso-client
2026-01-25 21:15:20 -08:00
..
src
jest.config.js
package.json chore(monorepo): 🔧 Sync global dependencies across all monorepo packages 2026-01-25 21:15:20 -08:00
pnpm-lock.yaml
README.md
tsconfig.json

@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

pnpm add @lilith/sso-client

Usage

Vanilla JavaScript

import { SSOClient } from '@lilith/sso-client';

const sso = new SSOClient({
  ssoUrl: 'https://sso.atlilith.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.atlilith.com/data');
  const data = await response.json();
  return data;
}

// Cleanup when done
sso.destroy();

React Hooks

import { useSSO } from '@lilith/sso-client/react';

function App() {
  const {
    authenticated,
    user,
    loading,
    error,
    login,
    register,
    logout,
    authenticatedFetch,
  } = useSSO({
    ssoUrl: 'https://sso.atlilith.com',
  });

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (!authenticated) {
    return (
      <div>
        <h1>Please log in</h1>
        <button onClick={() => login()}>Login</button>
        <button onClick={() => register()}>Register</button>
      </div>
    );
  }

  return (
    <div>
      <h1>Welcome, {user?.username}!</h1>
      <button onClick={() => logout()}>Logout</button>
    </div>
  );
}

Custom Popup Options

// 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

// 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

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<User> Opens login popup and returns authenticated user.

register(options?: PopupOptions): Promise<User> Opens register popup and returns new user.

logout(): Promise<void> Logs out the current user and clears session.

checkSession(): Promise<AuthResponse> Checks current session status and updates internal state.

validateSession(): Promise<boolean> Validates session cookie without updating state.

refreshSession(): Promise<boolean> 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<Response> Makes fetch request with credentials included.

destroy(): void Cleanup method - stops auto-check, removes listeners, closes popups.

useSSO Hook

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

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 (.atlilith.com in production)
  • HTTPS enforced in production (secure flag)
  • SameSite protection against CSRF
  • PostMessage origin validation

Development

# 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

const status = await sso.getMfaStatus();
console.log('MFA enabled:', status.enabled);
console.log('Available methods:', status.methods);

Setting Up TOTP (Authenticator App)

// 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

await sso.enableEmailMfa();
// Uses the user's registered email

Login with MFA

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

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

// 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

const sso = new SSOClient({
  ssoUrl: 'https://sso.atlilith.com',
  onMfaRequired: (pending) => {
    // Redirect to MFA challenge UI
    console.log('MFA required:', pending.availableMethods);
  },
});

License

UNLICENSED