import React, { useState, useEffect, useCallback } from 'react'; import { toast } from 'sonner'; import { api } from '@/api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { FieldRow, Toggle } from './adminShared'; const AUTHENTIK_ICON_URL = '/img/auth.png'; function defaultOidcRedirectUri() { if (typeof window === 'undefined') return ''; return `${window.location.origin}/api/auth/oidc/callback`; } function looksLikeOidcEndpoint(url) { const value = String(url || '').toLowerCase(); return /\/(?:authorize|token|userinfo|jwks|certs)\/?$/.test(value); } export default function AuthMethodsCard() { const [data, setData] = useState(null); const [form, setForm] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [testingOidc, setTestingOidc] = useState(false); const [oidcTest, setOidcTest] = useState(null); const load = useCallback(async () => { try { const d = await api.authModeConfig(); setData(d); setForm({ local_login_enabled: d.local_login_enabled !== false, oidc_login_enabled: !!d.oidc_login_enabled, oidc_provider_name: d.oidc_provider_name || 'authentik', oidc_issuer_url: d.oidc_issuer_url || '', oidc_client_id: d.oidc_client_id || '', oidc_client_secret: '', oidc_client_secret_clear: false, oidc_token_auth_method: d.oidc_token_auth_method || 'client_secret_basic', oidc_redirect_uri: d.oidc_redirect_uri || defaultOidcRedirectUri(), oidc_scopes: d.oidc_scopes || 'openid email profile groups', oidc_auto_provision: d.oidc_auto_provision !== false, oidc_admin_group: d.oidc_admin_group || '', oidc_default_role: d.oidc_default_role || 'user', }); } catch (err) { toast.error(err.message || 'Failed to load auth settings.'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const set = (k, v) => setForm(prev => ({ ...prev, [k]: v })); async function handleSave() { setSaving(true); try { const d = await api.setAuthMode(form); setData(d); setForm({ local_login_enabled: d.local_login_enabled !== false, oidc_login_enabled: !!d.oidc_login_enabled, oidc_provider_name: d.oidc_provider_name || 'authentik', oidc_issuer_url: d.oidc_issuer_url || '', oidc_client_id: d.oidc_client_id || '', oidc_client_secret: '', oidc_client_secret_clear: false, oidc_token_auth_method: d.oidc_token_auth_method || 'client_secret_basic', oidc_redirect_uri: d.oidc_redirect_uri || defaultOidcRedirectUri(), oidc_scopes: d.oidc_scopes || 'openid email profile groups', oidc_auto_provision: d.oidc_auto_provision !== false, oidc_admin_group: d.oidc_admin_group || '', oidc_default_role: d.oidc_default_role || 'user', }); toast.success('Auth method settings saved.'); } catch (err) { toast.error(err.message || 'Failed to save auth method settings.'); } finally { setSaving(false); } } async function handleTestOidc() { setTestingOidc(true); setOidcTest(null); try { const result = await api.testOidcConfig(form); setOidcTest(result); toast.success('authentik configuration test passed.'); } catch (err) { const result = err.data || { ok: false, error: err.message || 'OIDC configuration test failed.' }; setOidcTest(result); toast.error(result.error || 'OIDC configuration test failed.'); } finally { setTestingOidc(false); } } if (loading || !form) { return ( Loading auth settings… ); } const secretAvailable = form.oidc_client_secret.trim() ? true : form.oidc_client_secret_clear ? false : !!data?.oidc_client_secret_set; const oidcConfigured = !!( form.oidc_issuer_url.trim() && form.oidc_client_id.trim() && secretAvailable && form.oidc_redirect_uri.trim() ); const adminGroupConfigured = !!form.oidc_admin_group.trim(); const wouldLockOut = !form.local_login_enabled && !form.oidc_login_enabled; const cantDisableLocal = !form.local_login_enabled && (!oidcConfigured || !form.oidc_login_enabled || !adminGroupConfigured); const oidcEnabledButIncomplete = form.oidc_login_enabled && !oidcConfigured; const canSave = !wouldLockOut && !cantDisableLocal && !oidcEnabledButIncomplete && !saving; const canTestOidc = oidcConfigured && !testingOidc; const missingFields = [ !form.oidc_issuer_url.trim() && 'Issuer URL', !form.oidc_client_id.trim() && 'Client ID', !secretAvailable && 'Client Secret', !form.oidc_redirect_uri.trim() && 'Redirect URI', ].filter(Boolean); const issuerEndpointWarning = looksLikeOidcEndpoint(form.oidc_issuer_url); return (
Authentication Methods

Control local login and authentik/OIDC. Settings are saved in the database; environment variables only fill blank fields as bootstrap defaults.

{(data?.warnings?.length > 0 || wouldLockOut || cantDisableLocal) && (
{wouldLockOut && (

Cannot disable all login methods; at least one must remain enabled.

)} {cantDisableLocal && !wouldLockOut && (

Cannot disable local login without authentik/OIDC configured, enabled, and mapped to an admin group.

)} {oidcEnabledButIncomplete && (

authentik/OIDC needs {missingFields.join(', ')} before it can be enabled.

)} {data?.warnings?.map((w, i) => (

{w}

))}
)}
set('local_login_enabled', v)} label="Enable local login" /> {form.local_login_enabled ? 'Enabled' : 'Disabled'}
set('oidc_login_enabled', v)} label="Enable OIDC login" /> {!oidcConfigured ? 'Not fully configured' : form.oidc_login_enabled ? 'Enabled' : 'Disabled'}
authentik / OIDC configuration
set('oidc_provider_name', e.target.value)} placeholder="authentik" className="max-w-xs h-8 text-sm" />
set('oidc_issuer_url', e.target.value)} placeholder="https://yourURL.com/application/o/bills/.well-known/openid-configuration" className="max-w-xl h-8 text-sm" />

Use the authentik provider issuer URL or full discovery URL, for example https://yourURL.com/application/o/bills/.well-known/openid-configuration.

{issuerEndpointWarning && (

This looks like an authorization endpoint. In authentik, copy the provider issuer or OpenID Configuration URL.

)}
set('oidc_client_id', e.target.value)} placeholder="authentik client ID" className="max-w-xl h-8 text-sm" />
setForm(prev => ({ ...prev, oidc_client_secret: e.target.value, oidc_client_secret_clear: e.target.value ? false : prev.oidc_client_secret_clear, }))} placeholder="Leave blank to keep existing secret" className="h-8 text-sm" /> {data?.oidc_client_secret_set && !form.oidc_client_secret_clear ? 'Secret is set' : 'No secret saved'}

Advanced. Keep client_secret_basic unless your authentik provider explicitly requires client_secret_post.

set('oidc_redirect_uri', e.target.value)} placeholder={defaultOidcRedirectUri()} className="h-8 text-sm" />

Add this exact URL to the Redirect URIs allowed by authentik.

set('oidc_scopes', e.target.value)} placeholder="openid email profile groups" className="max-w-xl h-8 text-sm" />
set('oidc_admin_group', e.target.value)} placeholder="e.g. bill-tracker-admins" className="max-w-sm h-8 text-sm" />

Only users in this authentik group become app admins. Admin is never granted by default.

set('oidc_auto_provision', v)} label="Auto-provision users" /> {form.oidc_auto_provision ? 'Enabled' : 'Disabled'}

When enabled, valid authentik users are created in this app on first login.

Admin role only via admin group.
{data?.oidc_env_fallback_used && (
One or more blank database fields are currently using environment fallback values. Saving values here takes precedence.
)} {oidcTest && (
{oidcTest.ok ? `Configuration test passed for ${oidcTest.issuer || form.oidc_issuer_url}.` : oidcTest.error || 'Configuration test failed.'}
)}
); }