feat: add passkey offer step to onboarding wizard

After tenant creation, checks vendor auth policy and conditionally
shows a passkey enrollment offer screen before redirecting. User
can skip and set up later.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-27 08:55:24 +02:00
parent 76a62135ab
commit 60a800f757

View File

@@ -20,6 +20,7 @@ export function OnboardingPage() {
const [error, setError] = useState<string | null>(null);
const [slugAvailable, setSlugAvailable] = useState<boolean | null>(null);
const [checkingSlug, setCheckingSlug] = useState(false);
const [showPasskeyOffer, setShowPasskeyOffer] = useState(false);
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const slug = toSlug(name);
@@ -50,6 +51,17 @@ export function OnboardingPage() {
setLoading(true);
try {
await api.post<TenantResponse>('/onboarding/tenant', { name, slug });
// Check if passkeys are enabled in vendor policy
try {
const config = await fetch('/platform/api/config').then(r => r.json());
if (config.vendorAuthPolicy?.passkeyEnabled) {
setShowPasskeyOffer(true);
setLoading(false);
return; // Don't redirect yet
}
} catch {
// Ignore — proceed without passkey offer
}
// Tenant created — force a fresh OIDC sign-in so the Logto SDK gets
// new tokens that include the org membership just created. The existing
// Logto session cookie means the user won't see a login form — Logto
@@ -66,6 +78,34 @@ export function OnboardingPage() {
}
}
async function handleSkipPasskey() {
await signIn(`${window.location.origin}/platform/callback`);
}
if (showPasskeyOffer) {
return (
<div className={styles.page}>
<div className={styles.wrapper}>
<Card className={styles.card}>
<div className={styles.inner}>
<div style={{ textAlign: 'center' }}>
<h2 style={{ margin: '16px 0 8px' }}>Secure your account</h2>
<p style={{ color: 'var(--text-muted)', marginBottom: 24 }}>
Add a passkey to sign in faster with your fingerprint, face, or security key.
</p>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<Button variant="secondary" onClick={handleSkipPasskey}>
Set up later
</Button>
</div>
</div>
</Card>
</div>
</div>
);
}
return (
<div className={styles.page}>
<div className={styles.wrapper}>