Passkeys are no longer future technology. They already power authentication in major banking apps on phones, including Alpha-Bank, Tinkoff, and Sber. GitHub, Apple, and Google have also adopted them. Yet many web services still require users to create passwords with uppercase letters, numbers, and special characters, followed by monthly reset prompts.
Yaroslav Morozov, who builds Continental IT consulting and an internal product, decided to skip password friction when implementing authorization. The team chose Passkeys and completed the rollout in three days. This article delivers complete production code from database migration to the Face ID login button, using FastAPI on the backend and Next.js on the frontend.
Why Passkeys Matter
Passkeys offer superior security and convenience. Passwords can be phished on fake sites, while Passkeys bind cryptographic keys to the correct domain. Database leaks expose bcrypt hashes that attackers can crack, but private keys never leave the device. Users no longer need to invent, remember, or reset passwords; a fingerprint or facial scan suffices. Unlike reused passwords across sites, each Passkey pair is unique per domain.
According to the FIDO Alliance, more than 15 billion accounts already support Passkeys. Google, Apple, and Microsoft have integrated support at the operating system level. Technically, Passkeys implement the WebAuthn standard from the FIDO2 family.
How Passkeys Work
A Passkey consists of an asymmetric key pair. The private key resides in the device Secure Enclave and never leaves it. The public key is sent to the server during registration. During authentication the server issues a random 32-byte challenge. The device signs it after biometric confirmation, and the server verifies the signature with the stored public key. No password travels over the network.
Discoverable credentials enable automatic synchronization across devices via iCloud Keychain on Apple platforms or Google Password Manager on Android. Passkeys replace only the login-password step; after successful verification the backend still issues standard JWT access and refresh tokens.
Database Schema and Models
Two PostgreSQL tables store everything. The webauthn_credentials table holds credential_id, public_key, sign_count, transports, and device_name. The webauthn_challenges table manages one-time challenges with a five-minute TTL. SQLAlchemy models map these tables with proper relationships and cascade deletes.
Backend Service with py-webauthn
The service uses only three environment variables for RP ID, RP name, and origin. Custom exceptions handle verification failures, missing credentials, and deactivated accounts. Methods generate registration and authentication options, verify responses, save and pop challenges, and manage user credentials. All cryptographic operations rely on the mature py-webauthn library.


