From 4fb611110a240c9b287d9ff3d5a9aaac055d5847 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 25 Jun 2026 18:47:32 +0800 Subject: [PATCH] feat(auth): add "Sign in with SSO" entry to LoginForm Adds an SSO sign-in button to the login page that routes the entered email to its configured IdP (better-auth @better-auth/sso, by email domain) via POST /api/v1/auth/sign-in/sso and redirects to the provider's authorization endpoint. i18n label (labels.ssoButton); inline error when no provider matches the domain. UI half (PR-2) of the per-env external-IdP SSO mechanism; backend lands in framework #2322, design in ADR-0024. Verified end-to-end in a browser: button -> IdP login -> consent -> JIT federated session. Co-Authored-By: Claude Opus 4.8 --- packages/auth/src/LoginForm.tsx | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) 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 (
} {isLoading ? l.submittingButton : l.submitButton} + +