Skip to main content

Server

Installing#

This package is available on npm:

npm install @simplewebauthn/server

It can then be imported into all types of Node projects thanks to its support for both ES Module and CommonJS import patterns:

// ES Module (TypeScript, Babel, etc...)import SimpleWebAuthnServer from '@simplewebauthn/server';
// CommonJS (NodeJS)const SimpleWebAuthnServer = require('@simplewebauthn/server');

Documentation below will refer to the following TypeScript types. These are intended to be inspirational, a simple means of communicating the...types...of values you'll need to be capable of persisting in your database:

type UserModel = {  id: string;  username: string;  currentChallenge?: string;};
/** * It is strongly advised that authenticators get their own DB * table, ideally with a foreign key to a specific UserModel. * * "SQL" tags below are suggestions for column data types and * how best to store data received during registration for use * in subsequent authentications. */type Authenticator = {  // SQL: Encode to base64url then store as `TEXT`. Index this column  credentialID: Buffer;  // SQL: Store raw bytes as `BYTEA`/`BLOB`/etc...  credentialPublicKey: Buffer;  // SQL: Consider `BIGINT` since some authenticators return atomic timestamps as counters  counter: number;  // SQL: `VARCHAR(255)` and store string array as a CSV string  // ['usb' | 'ble' | 'nfc' | 'internal']  transports?: AuthenticatorTransport[];};

Identifying your RP#

Start by defining some constants that describe your "Relying Party" (RP) server to authenticators:

// Human-readable title for your websiteconst rpName = 'SimpleWebAuthn Example';// A unique identifier for your websiteconst rpID = 'localhost';// The URL at which registrations and authentications should occurconst origin = `https://${rpID}`;

These will be referenced throughout registrations and authentications to ensure that authenticators generate and return credentials specifically for your server.

info

The following instructions are for setting up SimpleWebAuthn for 2FA support. Guides for "Passwordless" and "Usernameless" support are coming soon.

Registration#

"Registration" is analogous to new account creation. Registration uses the following exported methods from this package:

import {  generateRegistrationOptions,  verifyRegistrationResponse,} from '@simplewebauthn/server';

Registration occurs in two steps:

  1. Generate registration options for the browser to pass to a supported authenticator
  2. Verify the authenticator's response

Each of these steps need their own API endpoints:

1. Generate registration options#

One endpoint (GET) needs to return the result of a call to generateRegistrationOptions():

// (Pseudocode) Retrieve the user from the database// after they've logged inconst user: UserModel = getUserFromDB(loggedInUserId);// (Pseudocode) Retrieve any of the user's previously-// registered authenticatorsconst userAuthenticators: Authenticator[] = getUserAuthenticators(user);
const options = generateRegistrationOptions({  rpName,  rpID,  userID: user.id,  userName: user.username,  // Don't prompt users for additional information about the authenticator  // (Recommended for smoother UX)  attestationType: 'indirect',  // Prevent users from re-registering existing authenticators  excludeCredentials: userAuthenticators.map(authenticator => ({    id: authenticator.credentialID,    type: 'public-key',    // Optional    transports: authenticator.transports,  })),});
// (Pseudocode) Remember the challenge for this usersetUserCurrentChallenge(user, options.challenge);
return options;

These options can be passed directly into @simplewebauthn/browser's startRegistration() method.

Support for custom challenges

Power users can optionally generate and pass in their own unique challenges as challenge when calling generateRegistrationOptions(). In this scenario options.challenge still needs to be saved to be used in verification as described below.

2. Verify registration response#

The second endpoint (POST) should accept the value returned by @simplewebauthn/browser's startRegistration() method and then verify it:

const { body } = req;
// (Pseudocode) Retrieve the logged-in userconst user: UserModel = getUserFromDB(loggedInUserId);// (Pseudocode) Get `options.challenge` that was saved aboveconst expectedChallenge: string = getUserCurrentChallenge(user);
let verification;try {  verification = await verifyRegistrationResponse({    credential: body,    expectedChallenge,    expectedOrigin: origin,    expectedRPID: rpID,  });} catch (error) {  console.error(error);  return res.status(400).send({ error: error.message });}
const { verified, registrationInfo } = verification;
Support for multiple origins and RP IDs

SimpleWebAuthn optionally supports verifying registrations from multiple origins and RP IDs! Simply pass in an array of possible origins and IDs for expectedOrigin and expectedRPID respectively.

If verification.verified is true, then save the credential data in registrationInfo to the database:

const { credentialPublicKey, credentialID, counter } = registrationInfo;
const newAuthenticator: Authenticator = {  credentialID,  credentialPublicKey,  counter,};
// (Pseudocode) Save the authenticator info so that we can// get it by user ID latersaveNewUserAuthenticatorInDB(user, newAuthenticator);

When finished, it's a good idea to return the verification status to the browser to display appropriate UI:

return { verified };

Supported Attestation Formats#

If attestationType is set to "direct" when generating registration options, the authenticator will return a more complex response containing an "attestation statement". This statement includes additional verifiable information about the authenticator.

Attestation statements are returned in one of several different formats. SimpleWebAuthn supports all current WebAuthn attestation formats, including:

  • Packed
  • TPM
  • Android Key
  • Android SafetyNet
  • Apple
  • FIDO U2F
  • None
info

Attestation statements are an advanced aspect of WebAuthn. You can ignore this concept if you're not particular about the kinds of authenticators your users can use for registration and authentication.

Authentication#

"Authentication" is analogous to existing account login. Authentication uses the following exported methods from this package:

import {  generateAuthenticationOptions,  verifyAuthenticationResponse,} from '@simplewebauthn/server';

Just like registration, authentication span two steps:

  1. Generate authentication options for the browser to pass to a FIDO2 authenticator
  2. Verify the authenticator's response

Each of these steps need their own API endpoints:

1. Generate authentication options#

One endpoint (GET) needs to return the result of a call to generateAuthenticationOptions():

// (Pseudocode) Retrieve the logged-in userconst user: UserModel = getUserFromDB(loggedInUserId);// (Pseudocode) Retrieve any of the user's previously-// registered authenticatorsconst userAuthenticators: Authenticator[] = getUserAuthenticators(user);
const options = generateAuthenticationOptions({  // Require users to use a previously-registered authenticator  allowCredentials: userAuthenticators.map(authenticator => ({    id: authenticator.credentialID,    type: 'public-key',    // Optional    transports: authenticator.transports,  })),  userVerification: 'preferred',});
// (Pseudocode) Remember this challenge for this usersetUserCurrentChallenge(user, options.challenge);
return options;

These options can be passed directly into @simplewebauthn/browser's startAuthentication() method.

Support for custom challenges

Power users can optionally generate and pass in their own unique challenges as challenge when calling generateAuthenticationOptions(). In this scenario options.challenge still needs to be saved to be used in verification as described below.

2. Verify authentication response#

The second endpoint (POST) should accept the value returned by @simplewebauthn/browser's startAuthentication() method and then verify it:

const { body } = req;
// (Pseudocode) Retrieve the logged-in userconst user: UserModel = getUserFromDB(loggedInUserId);// (Pseudocode) Get `options.challenge` that was saved aboveconst expectedChallenge: string = getUserCurrentChallenge(user);// (Pseudocode} Retrieve an authenticator from the DB that// should match the `id` in the returned credentialconst authenticator = getUserAuthenticator(user, body.id);
if (!authenticator) {  throw new Error(`Could not find authenticator ${body.id} for user ${user.id}`);}
let verification;try {  verification = await verifyAuthenticationResponse({    credential: body,    expectedChallenge,    expectedOrigin: origin,    expectedRPID: rpID,    authenticator,  });} catch (error) {  console.error(error);  return res.status(400).send({ error: error.message });}
const { verified, authenticationInfo } = verification;
Support for multiple origins and RP IDs

SimpleWebAuthn optionally supports verifying authentications from multiple origins and RP IDs! Simply pass in an array of possible origins and IDs for expectedOrigin and expectedRPID respectively.

If verification.verified is true, then update the user's authenticator's counter property in the DB:

const { newCounter } = authenticationInfo;
saveUpdatedAuthenticatorCounter(authenticator, newCounter);

When finished, it's a good idea to return the verification status to the browser to display appropriate UI:

return { verified };

Advanced functionality#

caution

The following functionality is opt-in and is not required for typical use! SimpleWebAuthn remains focused on simplifying working with the WebAuthn API, and the functionality covered so far will serve the majority of developers' use cases.

Some developers, though, may have more demanding requirements that require a higher degree of control over the types of authenticators users may utilize when registering or authenticating. The features below enable such advanced uses of SimpleWebAuthn.

MetadataService#

Metadata statements maintained by the FIDO Alliance can be referenced during registration to cross-reference additional information about authenticators used with SimpleWebAuthn. These statements contain cryptographically-signed "guarantees" about authenticators and what they are capable of, according to their manufacturer.

SimpleWebauthn includes support for the FIDO Alliance Metadata Service (version 3.0) API via its MetadataService:

import { MetadataService } from '@simplewebauthn/server';

This singleton service contains all of the logic necessary to interact with the MDS API, including signed data verification and automatic periodic refreshing of metadata statements.

info

Use of MetadataService is not required to use @simplewebauthn/server! This is opt-in functionality that enables a more strict adherence to FIDO specifications and may not be appropriate for your use case.

initialize()#

Simply call initialize() to enable MetadataService configured to use the official MDS API:

import { MetadataService } from '@simplewebauthn/server';
MetadataService.initialize().then(() => {  console.log('🔐 MetadataService initialized');});

MetadataService can also be initialized with optional URLs to other MDS-compatible servers, any local metadata statements you may maintain, or both:

import { MetadataService, MetadataStatement } from '@simplewebauthn/server';
const statements: MetadataStatement[] = [];
// Load in statements from JSON filestry {  const mdsMetadataPath = './metadata-statements';  const mdsMetadataFilenames = fs.readdirSync(mdsMetadataPath);  for (const statementPath of mdsMetadataFilenames) {    if (statementPath.endsWith('.json')) {      const contents = fs.readFileSync(`${mdsMetadataPath}/${statementPath}`, 'utf-8');      statements.push(JSON.parse(contents));    }  }} catch (err) {  // pass}
MetadataService.initialize({  mdsServers: ['https://mds-compatible-server.example.com'],  statements: statements,}).then(() => {  console.log('🔐 MetadataService initialized');});

Once MetadataService is initialized, verifyRegistrationResponse() will reference MDS metadata statements and error out if it receives authenticator responses with unexpected values.

caution

Make sure to set attestationType to "direct" when calling generateRegistrationOptions() to leverage the full power of metadata statements!

SettingsService#

The SettingsService singleton offers various methods for customizing SimpleWebAuthn's functionality.

setRootCertificates()#

Some registration response attestation statements can be validated via root certificates prescribed by the company responsible for the format. It is possible to use SettingsService to register custom root certificates that will be used for validating certificate paths in subsequent registrations with matching attestation formats:

import { SettingsService } from '@simplewebauthn/server';
// A Buffer, or PEM-formatted certificate stringconst appleCustomRootCert: Buffer | string = '...';SettingsService.setRootCertificates({  identifier: 'apple',  certificates: [appleCustomRootCert],});

The following values for identifier are supported:

"android-key" | "android-safetynet" | "apple" | "fido-u2f" | "packed" | "tpm" | "mds"

If root certificates have not been registered for an attestation statement format (or you set an empty array to one [e.g. []]) then certificate path validation will not occur.

info

This method can come in handy when an attestation format requires use of a root certificate that SimpleWebAuthn has not yet been updated to use.

SimpleWebAuthn includes known root certificates for the following such attestation formats:

  • "android-key"
  • "android-safetynet"
  • "apple"
  • "mds" (for use with MetadataService to validate MDS BLOBs)

getRootCertificates()#

This method returns existing root certificates for a specific identifier:

import { SettingsService } from '@simplewebauthn/server';
const appleCerts: string[] = SettingsService.getRootCertificates({ identifier: 'apple' });

The returned certificates will be PEM-formatted strings;

Additional API Documentation#

Lower-level API docs for this package are available here:

https://api-docs.simplewebauthn.dev/modules/_simplewebauthn_server.html