Lion Cipher Lab
Add passkey login to a web app you build (the relying-party side of WebAuthn)
- Identity & Access Management
- Intermediate
- ~75 min
- Free · no account required
Why it matters
Every 'add passkeys' ticket eventually lands on a developer, and the industry's guidance mostly stops at the marketing layer: passkeys are phishing-resistant, turn them on. That is true and it is not enough to ship with. The server half -- the relying party -- is where the real decisions live: what you store, what you compare the response against, what you do when the user's laptop goes in a river. This lab has you build that half end to end in about a hundred lines of Flask, so 'the browser handles it' stops being a hand-wave. Then it has you break your own server on purpose: point…
What you'll learn
- the four-party WebAuthn model: user, authenticator, browser, relying party
- what a relying party actually stores (credential id + public key + sign count, no shared secret)
- generating registration options: rp_id, user handle, challenge, pubKeyCredParams, authenticatorSelection
- calling navigator.credentials.create() and navigator.credentials.get() from a page
- verifying an attestation and an assertion server-side with py_webauthn
- why localhost is a secure context and needs no TLS certificate
- proving origin binding by pointing expected_origin at the wrong origin
- why discarding the challenge is what makes an assertion single-use
- signature counters, cloned-authenticator detection, and why synced passkeys report 0
- discoverable credentials (resident keys) vs the username-first flow
Steps in this lab
- The four parties, and the one that is your job
- Why localhost needs no TLS certificate (and why that is the ONLY exception)
- Create the project and install the two dependencies
- Write /register/options -- every field, and what it decides
- Write the page: navigator.credentials.create() and the base64url dance
- Write /register/verify -- three expectations, then store the public key
- Write the login half: /login/options and /login/verify
- Run it -- and what to do if you have no fingerprint reader
- Break it on purpose (1): point expected_origin at the wrong place
- Break it on purpose (2): stop discarding the challenge and replay a login
- The edges that decide whether a real rollout survives: recovery, UV, discoverability
- The judgment to walk away with