Stay organized with collections Save and categorize content based on your preferences.
Note: This article is part of a series on server-side passkey implementation. Explore the other articles in this series: Introduction to server-side passkey implementation and Server-side passkey registration.Here's a high-level overview of the key steps involved in passkey authentication:
navigator.credentials.get
on the web). After the user confirms passkey authentication, the passkey authentication call is resolved and returns a credential (PublicKeyCredential
). The credential contains an authentication assertion.The following sections dive into the specifics of each step.
Note: While it's possible to implement server-side passkeys functionality from scratch, we recommend that you rely on a library instead.In the examples throughout this article, we'll demonstrate using a JavaScript and TypeScript FIDO server-side library called SimpleWebAuthn.
In practice, a challenge is an array of random bytes, represented as an ArrayBuffer
object.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Important: You can rely on your FIDO server-side library for challenge creation. In SimpleWebAuthn, challenge creation takes place in generateAuthenticationOptions
. See details in Create credential request options. Out of precaution, SimpleWebAuthn uses 32-byte challenges which are more secure than the 16-bytes recommendation.
To ensure the challenge fulfills its purpose, you must:
Once you've created a challenge, save it in the user's session to verify it later.
Create credential request optionsCreate credential request options as a publicKeyCredentialRequestOptions
object.
To do so, rely on your FIDO server-side library. It will typically offer a utility function that can create these options for you. SimpleWebAuthn offers, for example, generateAuthenticationOptions
.
publicKeyCredentialRequestOptions
should contain all the information needed for passkey authentication. Pass this information to the function in your FIDO server-side library that's responsible for creating the publicKeyCredentialRequestOptions
object.
publicKeyCredentialRequestOptions
that require server-side work. Learn more about all the fields in Sign in with a passkey.
Some of publicKeyCredentialRequestOptions
' fields can be constants. Others should be dynamically defined on the server:
rpId
: Which RP ID you expect the credential to be associated with, for example example.com
. Authentication will only succeed if the RP ID you provide here matches the RP ID associated with the credential. To populate RP ID, use the same value as the RP ID you set in publicKeyCredentialCreationOptions
during credential registration.challenge
: A piece of data that the passkey provider will sign to prove the user holds the passkey at the time of the authentication request. Review details in Create the challenge.allowCredentials
: An array of acceptable credentials for this authentication. Pass an empty array to let the user select an available passkey from a list shown by the browser. Review Fetch a challenge from the RP server and Discoverable credentials deep dive for details.userVerification
: Indicates whether user verification using the device screen lock is "required", "preferred" or "discouraged". Review Fetch a challenge from the RP server.timeout
: How long (in milliseconds) the user can take to complete authentication. It should be reasonably generous, and shorter than the lifetime of the challenge
. The recommended default value is 5 minutes, but you can increase it — up to 10 minutes, which is still within the recommended range. Long timeouts make sense if you expect users to use the hybrid workflow, which typically takes a bit longer. If the operation times out, a NotAllowedError
will be thrown.Once you've created publicKeyCredentialRequestOptions
, send it to the client.
challenge
decoding happens client-side. Example code: create credential request options
We're using the SimpleWebAuthn library in our examples. Here, we hand over the creation of credential request options to its generateAuthenticationOptions
function.
challenge
creation are abstracted away, and live in SimpleWebAuthn's code. If you're curious, check SimpleWebAuthn's source code for generateAuthenticationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Verify and sign in the user
When navigator.credentials.get
resolves successfully on the client, it returns a PublicKeyCredential
object.
navigator.credentials.get
returns a PublicKeyCredential
.
The response
is an AuthenticatorAssertionResponse
. It represents the passkey provider's response to the client's instruction to create what's needed to try and authenticate with a passkey on the RP. It contains:
response.authenticatorData
andresponse.clientDataJSON
, like at the passkey registration step.response.signature
which contains a signature over these values.Send the PublicKeyCredential
object to the server.
PublicKeyCredential
object includes fields that are ArrayBuffer
s, so they aren't supported by JSON.stringify()
. You must manually encode them using base64URL
before you can be send them to the server over HTTPS.
On the server, do the following:
Suggested database schema. Learn more about this design in Server-side passkey registration.response.userHandle
in the PublicKeyCredential
object. In the Users table, look for the passkey_user_id
that matches userHandle
.id
present in the PublicKeyCredential
object. In the Public key credentials table, look for the credential id
that matches the credential id
present in the PublicKeyCredential
object. Then look for the corresponding user using the foreign key passkey_user_id
to your Users table.id
that matches the credential id
present in the PublicKeyCredential
object.Verify the authentication assertion. Hand over this verification step to your FIDO server-side library, which will typically offer a utility function for this purpose. SimpleWebAuthn offers, for example, verifyAuthenticationResponse
. Learn what's happening under the hood in Appendix: verification of the authentication response.
Delete the challenge whether verification is successful or not, to prevent replay attacks.
Sign in the user. If the verification was successful, update session information to mark the user as signed-in. You may also want to return a user
object to the client, so the frontend can use information associated with the newly signed-in user.
We're using the SimpleWebAuthn library in our examples. Here, we hand over verification of the authentication response to its verifyAuthenticationResponse
function.
verify()
method. SubtleCrypto is an interface of the Web Crypto API that provides low-level cryptographic functionality.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Base64URL decode some values
const authenticator = {
credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
credentialID: isoBase64URL.toBuffer(cred.id),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Appendix: verification of the authentication response
Verifying the authentication response consists of the following checks:
uv
(user verified) flag in authenticatorData
is true
. Check that the up
(user present) flag in authenticatorData
is true
, since user presence is always required for passkeys.response.signature
challenge
to prove the user holds the passkey. This is a simplification. In reality, what the authenticator signs is a concatenation of authenticatorData
, and of a hash of clientDataJSON
which contains the challenge.
To learn more about these steps, check SimpleWebAuthn's source code for verifyAuthenticationResponse
or dive into the complete list of verifications in the specification.
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-05-19 UTC.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-05-19 UTC."],[[["Passkeys are authenticated server-side by generating unique challenges, creating credential request options, and verifying user sign-in attempts."],["Servers use libraries like SimpleWebAuthn to generate challenges, create credential request options, and verify authentication assertions."],["The verification process ensures the request's validity and the user's identity before granting access and updating session information."],["Successful authentication using passkeys relies on matching RP IDs, origins, and verifying client data, authenticator data, and signatures."]]],[]]
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4