import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { apiRequest, showErrorToast } from '@/lib/utils'; import { toast } from 'sonner'; import { Plus, Trash2, Loader2, AlertCircle, Shield } from 'lucide-react'; import { Skeleton } from '../ui/skeleton'; import { Badge } from '../ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Textarea } from '@/components/ui/textarea'; interface SSOProvider { id: string; issuer: string; domain: string; providerId: string; organizationId?: string; oidcConfig?: { clientId: string; clientSecret: string; authorizationEndpoint: string; tokenEndpoint: string; jwksEndpoint?: string; userInfoEndpoint?: string; discoveryEndpoint?: string; scopes?: string[]; pkce?: boolean; }; samlConfig?: { entryPoint: string; cert: string; callbackUrl?: string; audience?: string; wantAssertionsSigned?: boolean; signatureAlgorithm?: string; digestAlgorithm?: string; identifierFormat?: string; }; mapping?: { id: string; email: string; emailVerified?: string; name?: string; image?: string; firstName?: string; lastName?: string; }; createdAt: string; updatedAt: string; } export function SSOSettings() { const [providers, setProviders] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showProviderDialog, setShowProviderDialog] = useState(false); const [isDiscovering, setIsDiscovering] = useState(false); const [headerAuthEnabled, setHeaderAuthEnabled] = useState(false); // Form states for new provider const [providerType, setProviderType] = useState<'oidc' | 'saml'>('oidc'); const [providerForm, setProviderForm] = useState({ // Common fields issuer: '', domain: '', providerId: '', organizationId: '', // OIDC fields clientId: '', clientSecret: '', authorizationEndpoint: '', tokenEndpoint: '', jwksEndpoint: '', userInfoEndpoint: '', discoveryEndpoint: '', scopes: ['openid', 'email', 'profile'], pkce: true, // SAML fields entryPoint: '', cert: '', callbackUrl: '', audience: '', wantAssertionsSigned: true, signatureAlgorithm: 'sha256', digestAlgorithm: 'sha256', identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', }); useEffect(() => { loadData(); }, []); const loadData = async () => { setIsLoading(true); try { const [providersRes, headerAuthStatus] = await Promise.all([ apiRequest('/sso/providers'), apiRequest<{ enabled: boolean }>('/auth/header-status').catch(() => ({ enabled: false })) ]); setProviders(Array.isArray(providersRes) ? providersRes : providersRes?.providers || []); setHeaderAuthEnabled(headerAuthStatus.enabled); } catch (error) { showErrorToast(error, toast); } finally { setIsLoading(false); } }; const discoverOIDC = async () => { if (!providerForm.issuer) { toast.error('Please enter an issuer URL'); return; } setIsDiscovering(true); try { const discovered = await apiRequest('/sso/discover', { method: 'POST', data: { issuer: providerForm.issuer }, }); setProviderForm(prev => ({ ...prev, authorizationEndpoint: discovered.authorizationEndpoint || '', tokenEndpoint: discovered.tokenEndpoint || '', jwksEndpoint: discovered.jwksEndpoint || '', userInfoEndpoint: discovered.userInfoEndpoint || '', discoveryEndpoint: discovered.discoveryEndpoint || `${providerForm.issuer}/.well-known/openid-configuration`, domain: discovered.suggestedDomain || prev.domain, })); toast.success('OIDC configuration discovered successfully'); } catch (error) { showErrorToast(error, toast); } finally { setIsDiscovering(false); } }; const createProvider = async () => { try { const requestData: any = { providerId: providerForm.providerId, issuer: providerForm.issuer, domain: providerForm.domain, organizationId: providerForm.organizationId || undefined, providerType, }; if (providerType === 'oidc') { requestData.clientId = providerForm.clientId; requestData.clientSecret = providerForm.clientSecret; requestData.authorizationEndpoint = providerForm.authorizationEndpoint; requestData.tokenEndpoint = providerForm.tokenEndpoint; requestData.jwksEndpoint = providerForm.jwksEndpoint; requestData.userInfoEndpoint = providerForm.userInfoEndpoint; requestData.discoveryEndpoint = providerForm.discoveryEndpoint; // Don't send scopes - let the backend handle provider-specific defaults requestData.pkce = providerForm.pkce; } else { requestData.entryPoint = providerForm.entryPoint; requestData.cert = providerForm.cert; requestData.callbackUrl = providerForm.callbackUrl || `${window.location.origin}/api/auth/sso/saml2/callback/${providerForm.providerId}`; requestData.audience = providerForm.audience || window.location.origin; requestData.wantAssertionsSigned = providerForm.wantAssertionsSigned; requestData.signatureAlgorithm = providerForm.signatureAlgorithm; requestData.digestAlgorithm = providerForm.digestAlgorithm; requestData.identifierFormat = providerForm.identifierFormat; } const newProvider = await apiRequest('/sso/providers', { method: 'POST', data: requestData, }); setProviders([...providers, newProvider]); setShowProviderDialog(false); setProviderForm({ issuer: '', domain: '', providerId: '', organizationId: '', clientId: '', clientSecret: '', authorizationEndpoint: '', tokenEndpoint: '', jwksEndpoint: '', userInfoEndpoint: '', discoveryEndpoint: '', scopes: ['openid', 'email', 'profile'], pkce: true, entryPoint: '', cert: '', callbackUrl: '', audience: '', wantAssertionsSigned: true, signatureAlgorithm: 'sha256', digestAlgorithm: 'sha256', identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', }); toast.success('SSO provider created successfully'); } catch (error) { showErrorToast(error, toast); } }; const deleteProvider = async (id: string) => { try { await apiRequest(`/sso/providers?id=${id}`, { method: 'DELETE' }); setProviders(providers.filter(p => p.id !== id)); toast.success('Provider deleted successfully'); } catch (error) { showErrorToast(error, toast); } }; if (isLoading) { return (
); } return (
{/* Header with status indicators */}

Authentication & SSO

Configure how users authenticate with your application

0 ? 'bg-green-500' : 'bg-muted'}`} /> {providers.length} Provider{providers.length !== 1 ? 's' : ''} configured
{/* Authentication Methods Overview */} Active Authentication Methods
{/* Email & Password - Always enabled */}
Email & Password Default
Always enabled
{/* Header Authentication Status */} {headerAuthEnabled && (
Header Authentication Auto-login
Via reverse proxy
)} {/* SSO Providers Status */}
0 ? 'bg-green-500' : 'bg-muted'}`} /> SSO/OIDC Providers
{providers.length > 0 ? `${providers.length} provider${providers.length !== 1 ? 's' : ''} configured` : 'Not configured'}
{/* Header Auth Info */} {headerAuthEnabled && ( Header authentication is enabled. Users authenticated by your reverse proxy will be automatically logged in. )} {/* SSO Providers */}
External Identity Providers Connect external OIDC/OAuth providers (Google, Azure AD, etc.) to allow users to sign in with their existing accounts
Add SSO Provider Configure an external identity provider for user authentication setProviderType(value as 'oidc' | 'saml')}> OIDC / OAuth2 SAML 2.0 {/* Common Fields */}
setProviderForm(prev => ({ ...prev, providerId: e.target.value }))} placeholder="google-sso" />
setProviderForm(prev => ({ ...prev, domain: e.target.value }))} placeholder="example.com" />
setProviderForm(prev => ({ ...prev, issuer: e.target.value }))} placeholder={providerType === 'oidc' ? "https://accounts.google.com" : "https://idp.example.com"} /> {providerType === 'oidc' && ( )}
setProviderForm(prev => ({ ...prev, organizationId: e.target.value }))} placeholder="org_123" />

Link this provider to an organization for automatic user provisioning

setProviderForm(prev => ({ ...prev, clientId: e.target.value }))} />
setProviderForm(prev => ({ ...prev, clientSecret: e.target.value }))} />
setProviderForm(prev => ({ ...prev, authorizationEndpoint: e.target.value }))} placeholder="https://accounts.google.com/o/oauth2/auth" />
setProviderForm(prev => ({ ...prev, tokenEndpoint: e.target.value }))} placeholder="https://oauth2.googleapis.com/token" />
setProviderForm(prev => ({ ...prev, pkce: checked }))} />

Redirect URL: {window.location.origin}/api/auth/sso/callback/{providerForm.providerId || '{provider-id}'}

{providerForm.issuer.includes('google.com') && (

Note: Google doesn't support the "offline_access" scope. The system will automatically use appropriate scopes.

)}
setProviderForm(prev => ({ ...prev, entryPoint: e.target.value }))} placeholder="https://idp.example.com/sso" />