Passkey introduction roadmap
Step-by-step conversion strategy required when expanding existing password/OTP-based authentication to Passkey

Introduction
Passkey is a means to simultaneously improve UX and security, but it is difficult to immediately replace the existing password/OTP ecosystem. In practice, “gradual transition” is essential due to subscription channels, device diversity, and recovery policies.
This article deals with a roadmap that operates separately between new sign-ups and existing user conversions. The goal is to increase the level of security without losing users.

Problem definition
The most frustrating points in the Passkey conversion project:
- Registration flow works well, but CS load rapidly increases due to lack of account recovery policy.
- Login failure inquiries increase as the multi-device synchronization characteristics are not considered.
- The priority of Passkey and existing OTP policy is ambiguous, creating a security hole.
- The conversion rate is low as new and existing user experiences are processed through the same screen.
The key is not to add an authentication method, but to redefine the "authentication policy state machine".
Key concepts
| steps | Goal | Policy Points |
|---|---|---|
| Phase 1 | Opt-in registration | Maintain existing password path |
| Phase 2 | Recommended Login | Passkey priority, OTP fallback |
| Phase 3 | security enforcement | Passkey required for high-risk operations |
| Phase 4 | Toggle default | New registration Passkey default |
Account recovery is always a separate policy. If a “lost device” scenario is not prepared, operational risks become greater than security.
Code example 1: Creating server registration options (WebAuthn)
import { generateRegistrationOptions } from "@simplewebauthn/server";
export async function createPasskeyRegistrationOptions(user: {
id: string;
email: string;
displayName: string;
}) {
return generateRegistrationOptions({
rpName: "8SPACE",
rpID: "www.8space.dev",
userID: user.id,
userName: user.email,
userDisplayName: user.displayName,
attestationType: "none",
authenticatorSelection: {
residentKey: "preferred",
userVerification: "preferred",
},
timeout: 60_000,
});
}
Code example 2: Authentication policy state transition example
type AuthState = "PASSWORD_ONLY" | "PASSKEY_ENROLLED" | "PASSKEY_REQUIRED";
export function nextAuthState(params: {
current: AuthState;
hasPasskey: boolean;
riskScore: number;
}): AuthState {
if (params.current === "PASSWORD_ONLY" && params.hasPasskey) {
return "PASSKEY_ENROLLED";
}
if (params.hasPasskey && params.riskScore >= 70) {
return "PASSKEY_REQUIRED";
}
return params.current;
}
Architecture flow
Tradeoffs
- Introduction of Passkey greatly increases phishing resistance, but initial operational complexity increases.
- A stronger recovery policy improves security, but may cause user friction.
- If the stage transition criteria are not indexed, sensory decisions such as “it looks good so we expand” occur.
Cleanup
Passkey conversion is not a project to add authentication methods, but a project to redesign authentication policies. By clearly dividing the transition phase and designing recovery policies and risk-based enforcement conditions together, security and UX can be achieved at the same time.
Image source
- Cover: source link
- License: Public domain / Author: U.S. government
- Note: After downloading the free license image from Wikimedia Commons, it was optimized to JPG at 1600px.