diff --git a/packages/auth/src/LoginForm.tsx b/packages/auth/src/LoginForm.tsx index 932a97844..f67d84f99 100644 --- a/packages/auth/src/LoginForm.tsx +++ b/packages/auth/src/LoginForm.tsx @@ -34,6 +34,8 @@ export interface LoginFormLabels { signUpText?: string; /** Divider label between social sign-in and email/password (defaults to "or") */ orText?: string; + /** Label for the SSO sign-in button (defaults to "Sign in with SSO") */ + ssoButton?: string; } export interface LoginFormProps { @@ -130,6 +132,7 @@ export function LoginForm({ noAccountText: labels.noAccountText ?? "Don't have an account?", signUpText: labels.signUpText ?? 'Sign up', orText: labels.orText ?? 'or', + ssoButton: labels.ssoButton ?? 'Sign in with SSO', }; const handleSubmit = async (e: React.FormEvent) => { @@ -147,6 +150,33 @@ export function LoginForm({ } }; + /** + * Federated SSO sign-in: routes the entered email to its configured IdP + * (better-auth `@better-auth/sso`, by email domain) and redirects the + * browser to the provider's authorization endpoint. Falls back to an inline + * error when no provider matches the domain. + */ + const handleSso = async () => { + setError(null); + try { + const base = window.location.pathname.replace(/\/login(?:\/.*)?$/, ''); + const res = await fetch('/api/v1/auth/sign-in/sso', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email, callbackURL: base + '/home' }), + credentials: 'include', + }); + const data = (await res.json().catch(() => ({}))) as { url?: string; message?: string }; + if (res.ok && typeof data.url === 'string') { + window.location.href = data.url; + return; + } + setError(data.message || 'No SSO provider is configured for this email domain.'); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }; + return (