Adding passkeys and TOTP to existing PHP accounts
A practical architecture for adding WebAuthn passkeys and authenticator-app TOTP without creating a second identity system or weakening recovery and session security.
Adding passkeys or time-based one-time passwords to a mature PHP application is not an isolated login-screen feature. The new factor must connect to existing accounts, sessions, recovery rules, role changes, device management, audit records, and support procedures. If those boundaries are skipped, a modern sign-in interface can sit on top of an unchanged weak account lifecycle.
Passkeys and TOTP solve different problems. A passkey uses a public-key credential bound to a relying party and verified through a WebAuthn ceremony. TOTP proves possession of a shared secret through a short time-based code. A product may offer one or both, but it should define the security goal and recovery model before implementing either.
Map the current identity lifecycle
Start with registration, email verification, password login, session creation, remember-me behavior, password reset, email change, account disablement, role changes, and administrative impersonation if it exists. Identify where account IDs are created and which table and service are authoritative. The new credentials should attach to that identity rather than creating a parallel user record.
Review session regeneration, cookie flags, inactivity and absolute expiry, concurrent sessions, and logout. Strong authentication cannot compensate for a session identifier that is not rotated after login or a persistent cookie that remains valid after account recovery.
List high-risk actions that may require recent authentication or a second factor: changing email, changing password, viewing recovery codes, adding a payout method, disabling a factor, granting an administrative role, or deleting an account. This is the step-up policy.
Model passkeys as account credentials
A user can have several passkeys across phones, computers, password managers, or hardware keys. Store each credential as a child of the existing account with its credential identifier, public key material, signature counter where used, transports or hints if needed, creation time, last-used time, user-visible label, and revocation state. Never store a private key; it remains with the authenticator.
The server creates a random challenge for registration or authentication and stores it temporarily with the intended account or anonymous ceremony context, relying-party details, purpose, and expiry. The response must be checked against that exact challenge. A challenge should be single-use, short-lived, generated with a cryptographically secure source, and invalidated after completion.
Verify the relying-party boundary
WebAuthn security depends on origin and relying-party validation. Configure the exact public application domain and expected HTTPS origin for production. Local development needs an intentional supported configuration; do not weaken production checks to make a temporary test host convenient.
Use a maintained WebAuthn library instead of implementing binary parsing and attestation logic from scratch. Validate the client data type, challenge, origin, relying-party hash, user presence, user verification policy, credential identifier, signature, and account binding. Decide whether attestation is actually required; many normal consumer applications can use a privacy-preserving default without collecting device provenance.
Design discoverable and account-bound sign-in
A passkey flow can begin after a user enters an identifier or use discoverable credentials where the browser and authenticator identify the account. The product should choose deliberately. An identifier-first flow can fit an existing login page and account-selection model. A discoverable flow can reduce friction but requires careful mapping of the returned user handle to the server-side account.
After a successful ceremony, create the same application session used by the existing login system, regenerate its identifier, record the authentication method and time, and apply the normal account status and role checks. Do not treat possession of a valid credential as permission to bypass disabled, deleted, or restricted account state.
Enroll TOTP through a confirmed secret
Generate a random TOTP secret on the server and present it through a QR code and manual entry value over an authenticated HTTPS session. Do not activate the factor when the QR code is merely displayed. Require the user to submit a valid current code first, then mark enrollment confirmed.
Protect the secret at rest using the application’s approved encryption strategy and keep encryption keys outside the database. Hashing alone is not sufficient because the server needs the secret to verify future codes. Avoid writing the secret or provisioning URI to logs, analytics, support tools, or screenshots.
Verification should allow only the small clock window justified by the product and infrastructure. Keep server time synchronized. Add rate limits by account and request context, record failed attempts safely, and prevent a valid code from being replayed within the accepted window when the risk model requires it.
Treat recovery as part of authentication
Recovery frequently becomes the easiest way around a strong factor. Decide what happens when every passkey is unavailable, a phone with the authenticator app is lost, the email account is compromised, or support receives an urgent request. Each recovery route should have evidence requirements, delays or notifications appropriate to the account risk, and a clear audit trail.
Recovery codes are one option. Generate them with strong randomness, display them once, store only hashes, let each code work once, and regenerate the set when requested. A recovery-code view or reset should require recent authentication. Do not send the full set through ordinary email.
When recovery completes, revoke or review existing sessions and credentials according to the product policy. Notify the account through an established channel without including secrets. High-value or administrative accounts may need a stricter manual process than ordinary users.
Build factor-management controls carefully
Users need to name, inspect, and revoke passkeys; see when a factor was added or last used; add another factor; generate new recovery codes; and disable TOTP when policy permits. Sensitive changes should require recent authentication and, where possible, confirmation with another existing factor.
A factor-management page should not expose raw credential IDs, public-key internals, TOTP secrets, or recovery-code hashes. Show device labels, dates, and recognizable method names. Record administrative changes separately and restrict support tools from silently adding a credential to someone else’s account.
Plan migration and fallback
Do not force every existing account into a new factor during the first deployment unless the organization has a staged enrollment and recovery plan. Start with optional enrollment or a defined cohort, observe browser compatibility and support issues, then increase policy requirements deliberately.
Keep the password path only as long as the product needs it, but do not describe it as harmless fallback. Rate-limit it, protect reset flows, and notify users about important authentication changes. If the product becomes passkey-first, define how accounts without a compatible device enroll and recover.
Test more than successful login
- Expired, reused, missing, and mismatched WebAuthn challenges.
- Wrong origin, relying party, credential, account handle, or signature.
- Account disabled after the ceremony begins.
- Multiple passkeys, revoked credentials, and device labels.
- TOTP codes outside the accepted window, repeated codes, and rate limits.
- Session regeneration, logout, remember-me behavior, and recovery revocation.
- Adding and removing factors under recent-authentication rules.
- Keyboard and screen-reader access for enrollment and error feedback.
- Supported mobile, desktop, cross-device, and password-manager journeys.
Operational checklist
- Attach every new credential to the existing authoritative account.
- Use maintained libraries and exact production relying-party configuration.
- Make challenges random, short-lived, purpose-bound, and single-use.
- Confirm TOTP enrollment with a valid code before activation.
- Encrypt recoverable secrets and keep keys outside the database.
- Rate-limit verification and protect factor-management actions.
- Design recovery, notifications, session revocation, and support access together.
- Roll out in stages and monitor failures without logging secrets.
Passkeys and TOTP are strongest when they become part of one coherent account system. The ceremony, factor, session, recovery path, and support process should all enforce the same identity and authorization decisions. That integration work matters more than adding another button to the login page.
