Written and maintained by CASRAI Editorial Board
Last updated
WebAuthn (Web Authentication) is a browser API, standardised by the W3C, that lets a website register and verify a user with a public-key credential instead of a password. The private key stays on the user’s authenticator (a built-in device or a security key), the server stores only the public key, and each sign-in signature is bound to the site’s origin, which is why WebAuthn is described as phishing resistant. This page walks through the registration and sign-in ceremonies in the order a developer or IT administrator implements them, lists what the server must verify, and maps the error cases you are most likely to hit.
It is written for research IT staff, developers of lab and repository portals, and security-minded administrators who need to understand how passkey and security-key sign-in actually works. Sources are the W3C Web Authentication Level 3 specification (W3C Recommendation dated 25 August 2026), the MDN Web Authentication API documentation and the FIDO Alliance passkeys page, all read on 7 November 2026.
What WebAuthn is used for in research
WebAuthn replaces or supplements password sign-in on any web application that holds access-controlled data. In a research setting that includes institutional single sign-on, data repositories, electronic lab notebook and LIMS portals, and grants or compliance systems. The specification describes the API as one for creating and using “strong, attested, scoped, public-key credentials”, where each credential is scoped to a single Relying Party (the website or service) and the user agent mediates every use. Authenticators must not act without user consent.
For campus federated login see the related guides on Shibboleth single sign-on and SAML authentication and InCommon identity federation; WebAuthn is the credential layer an identity provider can use to verify the person before it issues those assertions. For security obligations that attach to controlled research data, see CMMC compliance for research institutions, and for the underlying primitive see what is cryptography.
WebAuthn, passkey, FIDO2 and CTAP: what each term means
| Term | What it refers to | Source |
|---|---|---|
| WebAuthn | The W3C browser API (navigator.credentials.create() and navigator.credentials.get() with a publicKey option) that web applications call. |
W3C Web Authentication Level 3; MDN |
| CTAP | The protocol between the browser or device and an external authenticator such as a USB security key. Cross-device sign-in uses the CTAP 2.2 hybrid transport, which relies on Bluetooth Low Energy proximity. | FIDO Alliance |
| FIDO2 | WebAuthn plus CTAP together. | FIDO Alliance |
| Passkey | A FIDO sign-in credential, written as a common noun. Passkeys may be synced across a user’s devices or device-bound. By definition a passkey is always a discoverable credential. | FIDO Alliance; MDN |
| Discoverable credential (resident key) | A credential for which the authenticator itself stores the private key material, the username and the RP ID, so the browser can list accounts without the server supplying credential IDs. | MDN; W3C |
| Relying Party (RP) | The website or service that registers and verifies credentials. Its RP ID scopes each credential. | W3C |
| Authenticator | A cryptographic entity, in hardware or software, that can register a user with a given Relying Party. It is either a platform authenticator (on the device) or a roaming authenticator (for example a USB security key). | W3C |
The FIDO Alliance notes that security keys have held device-bound passkeys since 2019, that biometrics stay on the device, and that some regulations do not yet list passkeys as multi-factor authentication. Check the exact wording of the control you must satisfy before treating a passkey as an MFA factor.
Install or access
There is nothing to install on the client: WebAuthn is a built-in browser API exposed through navigator.credentials. Two access conditions apply.
- Secure context only. The API is available only over HTTPS (a secure context), in supporting browsers.
- Permissions Policy. Embedded content is governed by the
publickey-credentials-createandpublickey-credentials-getdirectives, whose default allowlist isself. Where a policy forbids the method, the returned promise rejects with aNotAllowedError.
On the server you need code that generates a random challenge, parses the response and verifies it. You can write this yourself against the specification or use a library; see the libraries section below.
Step-by-step: registration and sign-in
Registration (creating a credential)
- The Relying Party server sends the user and Relying Party information and a challenge to the web app over a secure channel. The challenge must be a buffer of random bytes at least 16 bytes long.
- The web app calls
navigator.credentials.create({ publicKey }). The options include thechallenge, therp(idandname), theuser(idas opaque bytes,name,displayName),pubKeyCredParams(COSE algorithm identifiers such as ES256, -7), an optionaltimeoutand an optionalexcludeCredentialslist. - After the authenticator obtains user consent, it generates a key pair and returns the public key and an optional signed attestation as a
PublicKeyCredential. - The web app forwards that object to the server.
- The server stores the public key against the user identity, after checking that the challenge matches the one it sent, that the origin is the expected origin, and that the signature and attestation use the correct certificate chain for the authenticator model.
const credential = await navigator.credentials.create({
publicKey: {
challenge: challengeFromServer,
rp: { id: "example.org", name: "Example Repository" },
user: { id: userIdBytes, name: "[email protected]", displayName: "A. User" },
pubKeyCredParams: [{ type: "public-key", alg: -7 }]
}
});
Sign-in (authentication)
- The server generates a new challenge and sends it to the browser, optionally with a list of acceptable credentials and hints about where to look (a built-in authenticator, or USB or BLE).
- The web app calls
navigator.credentials.get({ publicKey })with the challenge, an optionalrpId, an optionalallowCredentialslist and auserVerificationsetting. - If the authenticator holds a matching credential and the user consents, it signs the challenge and returns an assertion as a
PublicKeyCredential. - The web app forwards the assertion to the server.
- The server validates it and, if every check passes, the sign-in succeeds.
const assertion = await navigator.credentials.get({
publicKey: {
challenge: challengeFromServer,
rpId: "example.org",
allowCredentials: [{ type: "public-key", id: credentialIdBytes }],
userVerification: "required"
}
});
Passkey autofill (conditional mediation)
To offer passkeys in the browser’s autofill list, mark the username field with autocomplete="username webauthn", omit allowCredentials, and call navigator.credentials.get() with mediation: "conditional". This works with discoverable credentials. Feature-detect first with PublicKeyCredential.getClientCapabilities() and test its conditionalGet property. A conditional create call (mediation: "conditional" on create()) is not allowed from a cross-origin iframe.
Output and how to read it
Both calls resolve to a PublicKeyCredential. Its response property holds the interesting part: an AuthenticatorAttestationResponse after registration (the new public key and optional attestation) or an AuthenticatorAssertionResponse after sign-in (the signed assertion). Everything is sent to the server as bytes; the browser never decides whether the user is who they claim to be.
Server-side verification checklist
| Ceremony | What the server must verify | Why |
|---|---|---|
| Registration | The challenge equals the one it issued | Ties the response to this attempt |
| Registration | The origin is the expected origin | Binds the credential to your site |
| Registration | Signature and attestation use the correct certificate chain for the authenticator model | Lets the Relying Party check authenticator provenance |
| Sign-in | The stored public key validates the authenticator’s signature | Proves possession of the private key |
| Sign-in | The signed challenge matches the one the server generated | Prevents replay of an old assertion |
| Sign-in | The Relying Party ID is the one expected for this service | Credentials are scoped to a single Relying Party |
User verification matters when you want the sign-in to count as more than possession of the device. The specification’s sample code sets userVerification: "preferred" and notes this is the default. If the authenticator reports that user verification is not initialised, the user-verified flag must not be relied on as an authentication factor.
Troubleshooting
| Symptom | Documented cause | What to check |
|---|---|---|
navigator.credentials is unavailable or the call fails |
The API requires a secure context | Serve the page over HTTPS |
NotAllowedError inside an embedded frame |
A Permissions Policy forbids publickey-credentials-create or publickey-credentials-get; the default allowlist is self |
Review the Permissions-Policy header and the iframe allow attribute |
NotAllowedError on conditional create |
Conditional create is not allowed from a cross-origin iframe | Call it from a same-origin top-level page |
| Prompt disappears before the user responds | Pending calls are cancelled if the browser window loses focus | Keep the window focused and retry |
| Server rejects a valid-looking response | Challenge, origin or RP ID differs from what the server expects | Compare the values in the checklist above; make sure the rp.id you register with is the one you verify against |
| User verification flag cannot be trusted | The authenticator’s user verification is not initialised | Do not count the flag as an authentication factor; require verification or add another factor |
Libraries for the server and browser
SimpleWebAuthn provides two complementary libraries, @simplewebauthn/server and @simplewebauthn/browser, intended to reduce the work of adding WebAuthn to a website. Check the project’s repository for its current licence and supported runtimes before adopting it. You can also implement the ceremonies directly against the browser API and the specification, as described above.
Frequently asked questions
Is WebAuthn the same as a passkey?
No. WebAuthn is the W3C API a site calls. A passkey is a FIDO sign-in credential built on that API, and by definition it is a discoverable credential. FIDO2 is the umbrella of WebAuthn plus CTAP.
What does WebAuthn store on the server?
Only the public key and the identity it is coupled to, together with the credential record the server needs for verification. The private key never leaves the authenticator.
Why is WebAuthn phishing resistant?
Each credential is scoped to one Relying Party and the signature is bound to the origin, so a lookalike site cannot obtain a valid assertion for your site.
Do users need to install anything?
No. It is a built-in browser API available over HTTPS. Users need an authenticator: a platform authenticator built into the device or a roaming security key.
Can a security key be a passkey?
Yes. The FIDO Alliance states that security keys have held device-bound passkeys since 2019.
Sources
- W3C, Web Authentication: An API for accessing Public Key Credentials, Level 3 (W3C Recommendation, 25 August 2026), read 7 November 2026.
- MDN Web Docs, Web Authentication API, read 7 November 2026.
- FIDO Alliance, Passkeys, read 7 November 2026.
- SimpleWebAuthn, documentation, read 7 November 2026.







