
Implementing Passwordless Login: A Step-by-Step Guide for Engineers
From concept to production: how to implement email magic links, one-time codes, and WebAuthn-based passwordless flows with security and UX best practices.
Implementing Passwordless Login: A Step-by-Step Guide for Engineers
Passwordless authentication has moved from novelty to best practice for many user-facing applications. It reduces friction, eliminates password reuse risk, and pairs well with modern device security. This guide walks through the design decisions, implementation steps, and operational considerations for three popular passwordless patterns: email magic links, one-time passcodes (OTPs), and WebAuthn/FIDO2.
1. Choose the right pattern for your users
Not every app needs WebAuthn. Choose based on user device capabilities and security requirements:
- Email magic links: High convenience, works anywhere with email. Good for consumer apps or low-risk workflows.
- OTPs (SMS or email): Familiar and simple but vulnerable to SIM swaps when using SMS. Prefer email for less risk.
- WebAuthn/FIDO2: Phishing-resistant and strong cryptographic guarantees. Best for high-security or enterprise flows.
2. Designing magic links securely
Magic links must be cryptographically secure and short-lived. Follow these recommendations:
- Generate a single-use token stored server-side with an expiration (e.g., 10 minutes).
- Embed the token in a link that points to a one-time endpoint which validates, consumes the token, and issues a session.
- Limit token scope to a single device or browser by binding to a fingerprint if you require stronger assurance.
- Rate-limit requests by IP or email to prevent abuse and enumerations.
3. Implementing OTPs safely
OTPs are easy to implement but carry risks:
- Prefer email OTP over SMS for security-sensitive apps unless you use carrier-level protections.
- Use time-limited codes (TOTP) if possible and allow a small time skew window.
- Store hashed versions of codes server-side (not plaintext) and mark them consumed on use.
4. WebAuthn integration
WebAuthn provides a high-assurance key-based model. Implementation steps:
- Register: generate a challenge, call navigator.credentials.create(), and store the relying party's credential ID and public key.
- Authenticate: generate a new challenge, call navigator.credentials.get(), verify the attestation or assertion, and validate the signature using the stored public key.
- Handle key recovery: provide backup options (recovery codes) and multi-device registration if necessary.
5. UX considerations
Passwordless must be unobtrusive. Provide clear microcopy: tell users to check spam folders, set realistic expiration times, and show success/failure states. For WebAuthn, offer fallbacks and explain the trade-offs for users who opt in to device keys.
6. Security best practices
- Use TLS everywhere.
- Store secrets and tokens encrypted at rest.
- Validate redirect URIs strictly to prevent open redirect abuses.
- Log authentications and failed attempts for anomaly detection.
7. Testing and monitoring
Test for edge cases: expired tokens, link replay, device time skew, and network interruptions. Monitor adoption and failure rates. If adoption for WebAuthn is low, instrument flows to understand drop-off points and improve onboarding.
8. Migration and coexistence
Many organizations adopt passwordless gradually. Allow both password and passwordless flows during migration. Encourage users to register device keys and provide incentives like faster sign-in to increase adoption.
Conclusion
Passwordless can reduce risk while improving user experience — but implementation details matter. Start with simple magic links for broad coverage, add WebAuthn where security matters most, and instrument adoption and abuse patterns to iterate.
"The best authentication is the one users actually use — but the second best is the one attackers can't phish."