Server-side WebAuthn functionality for Node.js, Deno, and other JavaScript runtimes. Provides functions to generate WebAuthn options, verify responses, and manage authenticator metadata.
The server package also exports helper functions for advanced use cases:
import {
generateChallenge,
generateUserID,
convertAAGUIDToString,
decodeAttestationObject,
decodeClientDataJSON,
parseAuthenticatorData,
toHash,
convertCOSEtoPKCS,
verifySignature,
isoBase64URL,
isoUint8Array,
} from "@simplewebauthn/server/helpers";Generates options for WebAuthn registration ceremonies.
/**
* Prepare a value to pass into navigator.credentials.create(...) for authenticator registration
*/
function generateRegistrationOptions(options: {
rpName: string;
rpID: string;
userName: string;
userID?: Uint8Array;
challenge?: string | Uint8Array;
userDisplayName?: string;
timeout?: number;
attestationType?: 'direct' | 'enterprise' | 'none';
excludeCredentials?: {
id: Base64URLString;
transports?: AuthenticatorTransportFuture[];
}[];
authenticatorSelection?: AuthenticatorSelectionCriteria;
extensions?: AuthenticationExtensionsClientInputs;
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
preferredAuthenticatorType?: 'securityKey' | 'localDevice' | 'remoteDevice';
}): Promise<PublicKeyCredentialCreationOptionsJSON>;
type GenerateRegistrationOptionsOpts = Parameters<typeof generateRegistrationOptions>[0];Usage Examples:
import { generateRegistrationOptions } from "@simplewebauthn/server";
// Basic registration options
const registrationOptions = await generateRegistrationOptions({
rpName: "My App",
rpID: "example.com",
userName: "user@example.com",
userDisplayName: "John Doe",
});
// Advanced registration with exclusions and authenticator selection
const advancedOptions = await generateRegistrationOptions({
rpName: "My App",
rpID: "example.com",
userName: "user@example.com",
userDisplayName: "John Doe",
attestationType: "direct",
excludeCredentials: existingCredentials.map(cred => ({
id: cred.id,
transports: cred.transports,
})),
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
userVerification: "required",
},
timeout: 120000,
});Verifies WebAuthn registration responses from the client.
/**
* Verify that the user has legitimately completed the registration process
*/
function verifyRegistrationResponse(options: {
response: RegistrationResponseJSON;
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
expectedOrigin: string | string[];
expectedRPID?: string | string[];
expectedType?: string | string[];
requireUserPresence?: boolean;
requireUserVerification?: boolean;
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
}): Promise<VerifiedRegistrationResponse>;
interface VerifiedRegistrationResponse {
verified: boolean;
registrationInfo?: {
fmt: AttestationFormat;
counter: number;
aaguid: string;
credentialID: Uint8Array;
credentialPublicKey: Uint8Array;
userVerified: boolean;
credentialDeviceType: CredentialDeviceType;
credentialBackedUp: boolean;
};
}Usage Examples:
import { verifyRegistrationResponse } from "@simplewebauthn/server";
const verification = await verifyRegistrationResponse({
response: registrationResponseFromClient,
expectedChallenge: expectedChallenge,
expectedOrigin: "https://example.com",
expectedRPID: "example.com",
requireUserVerification: true,
});
if (verification.verified && verification.registrationInfo) {
// Store credential in database
await saveCredential({
id: verification.registrationInfo.credentialID,
publicKey: verification.registrationInfo.credentialPublicKey,
counter: verification.registrationInfo.counter,
deviceType: verification.registrationInfo.credentialDeviceType,
backedUp: verification.registrationInfo.credentialBackedUp,
});
}Generates options for WebAuthn authentication ceremonies.
/**
* Prepare a value to pass into navigator.credentials.get(...) for authenticator authentication
*/
function generateAuthenticationOptions(options: {
rpID: string;
allowCredentials?: {
id: Base64URLString;
transports?: AuthenticatorTransportFuture[];
}[];
challenge?: string | Uint8Array;
timeout?: number;
userVerification?: 'required' | 'preferred' | 'discouraged';
extensions?: AuthenticationExtensionsClientInputs;
}): Promise<PublicKeyCredentialRequestOptionsJSON>;
type GenerateAuthenticationOptionsOpts = Parameters<typeof generateAuthenticationOptions>[0];Usage Examples:
import { generateAuthenticationOptions } from "@simplewebauthn/server";
// Basic authentication options (passwordless)
const authOptions = await generateAuthenticationOptions({
rpID: "example.com",
userVerification: "preferred",
});
// Authentication with specific credentials (second factor)
const specificAuthOptions = await generateAuthenticationOptions({
rpID: "example.com",
allowCredentials: userCredentials.map(cred => ({
id: cred.id,
transports: cred.transports,
})),
userVerification: "required",
timeout: 60000,
});Verifies WebAuthn authentication responses from the client.
/**
* Verify that the user has legitimately completed the authentication process
*/
function verifyAuthenticationResponse(options: {
response: AuthenticationResponseJSON;
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
expectedOrigin: string | string[];
expectedRPID?: string | string[];
credential: {
id: Base64URLString;
publicKey: Uint8Array;
counter: number;
transports?: AuthenticatorTransportFuture[];
};
requireUserPresence?: boolean;
requireUserVerification?: boolean;
advancedFIDOConfig?: {
userVerification?: 'required' | 'preferred' | 'discouraged';
};
}): Promise<VerifiedAuthenticationResponse>;
interface VerifiedAuthenticationResponse {
verified: boolean;
authenticationInfo?: {
newCounter: number;
userVerified: boolean;
credentialDeviceType: CredentialDeviceType;
credentialBackedUp: boolean;
};
}Usage Examples:
import { verifyAuthenticationResponse } from "@simplewebauthn/server";
const credential = await getCredentialFromDB(credentialID);
const verification = await verifyAuthenticationResponse({
response: authenticationResponseFromClient,
expectedChallenge: expectedChallenge,
expectedOrigin: "https://example.com",
expectedRPID: "example.com",
credential: {
id: credential.id,
publicKey: credential.publicKey,
counter: credential.counter,
transports: credential.transports,
},
requireUserVerification: true,
});
if (verification.verified && verification.authenticationInfo) {
// Update counter in database
await updateCredentialCounter(
credentialID,
verification.authenticationInfo.newCounter
);
// User is authenticated
return loginUser(userID);
}Service for managing FIDO Metadata Service (MDS) data to validate authenticators.
/**
* Global service for interacting with the FIDO Metadata Service
*/
interface MetadataService {
/**
* Prepare the service to handle remote MDS servers and/or cache local metadata statements
*/
initialize(options?: {
verificationMode?: VerificationMode;
mdsServers?: string[];
statements?: MetadataStatement[];
}): Promise<void>;
/**
* Get a metadata statement for a given AAGUID
*/
getStatement(aaguid: string): Promise<MetadataStatement | undefined>;
/**
* Get all cached metadata statements
*/
getStatements(): Promise<MetadataStatement[]>;
}
type VerificationMode = 'permissive' | 'strict';
// Exported singleton instance
const MetadataService: MetadataService;Usage Examples:
import { MetadataService } from "@simplewebauthn/server";
// Initialize with default FIDO MDS
await MetadataService.initialize();
// Initialize with custom verification mode
await MetadataService.initialize({
verificationMode: 'strict', // Only allow registered AAGUIDs
mdsServers: ['https://mds.fidoalliance.org/'],
});
// Get metadata for a specific authenticator
const statement = await MetadataService.getStatement(aaguid);
if (statement) {
console.log("Authenticator:", statement.description);
console.log("Attestation types:", statement.attestationTypes);
}Service for managing root certificates and attestation format settings.
/**
* Global service for managing attestation format settings
*/
interface SettingsService {
/**
* Set potential root certificates for attestation formats that use them
*/
setRootCertificates(opts: {
identifier: RootCertIdentifier;
certificates: (Uint8Array | string)[];
}): void;
/**
* Get any registered root certificates for the specified attestation format
*/
getRootCertificates(opts: {
identifier: RootCertIdentifier;
}): string[];
}
type RootCertIdentifier = AttestationFormat | 'mds';
// Exported singleton instance
const SettingsService: SettingsService;Usage Examples:
import { SettingsService } from "@simplewebauthn/server";
// Add custom root certificates for Android attestation
SettingsService.setRootCertificates({
identifier: 'android-key',
certificates: [customRootCertPEM],
});
// Get root certificates for a format
const certs = SettingsService.getRootCertificates({
identifier: 'apple',
});/**
* Supported crypto algo identifiers
* See https://w3c.github.io/webauthn/#sctn-alg-identifier
* and https://www.iana.org/assignments/cose/cose.xhtml#algorithms
*/
const supportedCOSEAlgorithmIdentifiers: COSEAlgorithmIdentifier[] = [
-8, // EdDSA
-7, // ECDSA w/ SHA-256
-36, // ECDSA w/ SHA-512
-37, // RSASSA-PSS w/ SHA-256
-38, // RSASSA-PSS w/ SHA-384
-39, // RSASSA-PSS w/ SHA-512
-257, // RSASSA-PKCS1-v1_5 w/ SHA-256
-258, // RSASSA-PKCS1-v1_5 w/ SHA-384
-259, // RSASSA-PKCS1-v1_5 w/ SHA-512
-65535 // RSASSA-PKCS1-v1_5 w/ SHA-1 (Deprecated)
];import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse,
MetadataService,
} from "@simplewebauthn/server";
// Initialize metadata service
await MetadataService.initialize();
// Registration flow
app.post('/webauthn/register/begin', async (req, res) => {
const options = await generateRegistrationOptions({
rpName: "My App",
rpID: "example.com",
userName: req.user.email,
userDisplayName: req.user.name,
excludeCredentials: req.user.credentials?.map(cred => ({
id: cred.id,
transports: cred.transports,
})),
});
req.session.challenge = options.challenge;
res.json(options);
});
app.post('/webauthn/register/finish', async (req, res) => {
const verification = await verifyRegistrationResponse({
response: req.body,
expectedChallenge: req.session.challenge,
expectedOrigin: "https://example.com",
expectedRPID: "example.com",
});
if (verification.verified && verification.registrationInfo) {
// Save credential to database
await saveCredential(req.user.id, verification.registrationInfo);
res.json({ verified: true });
} else {
res.status(400).json({ error: "Registration failed" });
}
});
// Authentication flow
app.post('/webauthn/authenticate/begin', async (req, res) => {
const options = await generateAuthenticationOptions({
rpID: "example.com",
allowCredentials: req.user.credentials?.map(cred => ({
id: cred.id,
transports: cred.transports,
})),
});
req.session.challenge = options.challenge;
res.json(options);
});
app.post('/webauthn/authenticate/finish', async (req, res) => {
const credential = await getCredential(req.body.id);
const verification = await verifyAuthenticationResponse({
response: req.body,
expectedChallenge: req.session.challenge,
expectedOrigin: "https://example.com",
expectedRPID: "example.com",
credential: credential,
});
if (verification.verified) {
// Update counter and log in user
await updateCredentialCounter(credential.id, verification.authenticationInfo.newCounter);
req.session.loggedIn = true;
res.json({ verified: true });
} else {
res.status(400).json({ error: "Authentication failed" });
}
});