> [!NOTE]
> **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](https://developers.google.com/identity/passkeys/developer-guides/server-introduction) and [Server-side passkey authentication](https://developers.google.com/identity/passkeys/developer-guides/server-authentication).   
> This article focuses on server-side functionality. To learn about the overall passkey registration flow including client-side steps, review [Create a passkey for passwordless logins](https://web.dev/articles/passkey-registration) for the web, or [Registration flows with Credential Manager](https://developer.android.com/training/sign-in/passkeys#registration-flows) for an Android app.

## Overview

> [!IMPORTANT]
> **Key Term:** The *relying party (RP)* is your web or mobile application. The web [specification](https://www.w3.org/TR/webauthn/#webauthn-relying-party) defines it as "the entity whose web application utilizes the WebAuthn API to register and authenticate users".

Here's a high-level overview of the key steps involved in passkey registration:

![Passkey registration flow](https://developers.google.com/identity/passkeys/developer-guides/images/server-registration.png)

- Define options to create a passkey. Send them to the client, so you can pass them to your passkey creation call: the WebAuthn API call `navigator.credentials.create` on the web, and [`credentialManager.createCredential`](https://developer.android.com/reference/android/credentials/CredentialManager#createCredential(android.content.Context,%20android.credentials.CreateCredentialRequest,%20android.os.CancellationSignal,%20java.util.concurrent.Executor,%20android.os.OutcomeReceiver%3Candroid.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException%3E)%60) on Android. After the user confirms creation of the passkey, the passkey creation call is resolved and returns a credential [`PublicKeyCredential`](https://www.w3.org/TR/webauthn/#iface-pkcredential).
- Verify the credential and store it on the server.

The following sections dive into the specifics of each step.

> [!NOTE]
> **Note:** While it's possible to implement server-side passkeys functionality from scratch, [we recommend that you rely on a library instead](https://developers.google.com/identity/passkeys/developer-guides/server-introduction#server-side_libraries).  
>
> In the examples throughout this article, we'll demonstrate using a JavaScript and TypeScript FIDO server-side library called [SimpleWebAuthn](https://simplewebauthn.dev/docs/packages/server).  
> While your specific server-side implementation may look slightly different depending on your chosen stack, the core concepts and key steps remain consistent. Refer to the documentation for your preferred library for detailed guidance.

## Create credential creation options

The first step you need to take on the server is to create a `PublicKeyCredentialCreationOptions` 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, [`generateRegistrationOptions`](https://simplewebauthn.dev/docs/packages/server#1-generate-registration-options).

`PublicKeyCredentialCreationOptions` should include everything that's needed for passkey creation: information about the user, about the RP, and a configuration for the properties of the credential you're creating. Once you've defined all of these, pass them as needed to the function in your FIDO server-side library that's responsible for creating the `PublicKeyCredentialCreationOptions` object.

> [!NOTE]
> **Note:** In this article, we focus on the fields of `PublicKeyCredentialCreationOptions` that require server-side work. For example, we omit `authenticatorSelection.authenticatorAttachment`. It defines the type of authenticator you want your users to rely on. It will typically be the same across all your users, so it can be a constant set once in your server code, which is why we don't cover it in detail here.   
> `authenticatorAttachment`---and in general the value of all the fields in `PublicKeyCredentialCreationOptions`---should be carefully selected, because they will define the type of credential you're creating and its behavior. Read more in [Create a passkey for passwordless logins](https://web.dev/articles/passkey-registration) (web) and [Sign in your user with Credential Manager](https://developer.android.com/training/sign-in/passkeys#format-json-request) (Android).

Some of `PublicKeyCredentialCreationOptions`' fields can be constants. Others should be dynamically defined on the server:

- **`rpId`** : To populate the RP ID on the server, use server-side functions or variables that give you the hostname of your web application, such as `example.com`.
- **`user.name`** and **`user.displayName`:** To populate these fields, use your signed-in user's session information (or the new user account information, if the user is creating a passkey on signup). `user.name` is typically an email address, and is unique for the RP. `user.displayName` is a user-friendly name. Note that not all platforms will use `displayName`.
- **`user.id`** : A random, unique string generated upon account creation. It should be permanent, unlike a username that may be editable. The user ID identifies an account, but [should not contain any personally identifiable information (PII)](https://www.w3.org/TR/webauthn/#sctn-user-handle-privacy). You likely already have a user ID in your system, but if needed, create one specifically for passkeys to keep it free of any PII.
- **`excludeCredentials`** : A list of existing credentials' IDs to prevent duplicating a passkey from the passkey provider. To populate this field, look up in your database existing credentials for this user. Review details at [Prevent creation of a new passkey if one already exists](https://web.dev/articles/webauthn-exclude-credentials).
- **`challenge`** : For credential registration, the challenge is not relevant unless you use attestation, a more advanced technique to verify a passkey provider's identity and the data it emits. However, even if you don't use attestation, the challenge is still a required field. Instructions for creating a secure challenge for authentication are available in [Server-side passkey authentication](https://developers.google.com/identity/passkeys/developer-guides/server-authentication#create_the_challenge).

### Encoding and decoding

![PublicKeyCredentialCreationOptions sent by the server](https://developers.google.com/identity/passkeys/developer-guides/images/server-publicKeyCredentialCreationOptions.png) `PublicKeyCredentialCreationOptions` sent by the server. Binary fields like `challenge` and `user.id` are encoded server-side into `base64URL` so that options can be delivered as JSON over HTTPS.

The standard WebAuthn `PublicKeyCredentialCreationOptions` object includes fields that are `ArrayBuffer`s, which cannot be directly serialized with `JSON.stringify()`. To send these options over HTTPS, binary fields must be encoded as `base64URL` strings:

- **On the server** , your FIDO server-side library (such as SimpleWebAuthn) automatically produces a JSON-serializable options object (e.g. `PublicKeyCredentialCreationOptionsJSON`) with Base64URL-encoded fields.
- **On the client** , modern browsers support [`PublicKeyCredential.parseCreationOptionsFromJSON()`](https://w3c.github.io/webauthn/#sctn-parseCreationOptionsFromJSON) to convert the JSON options directly into `PublicKeyCredentialCreationOptions`. When using client-side libraries such as `@simplewebauthn/browser`, functions like `startRegistration({ optionsJSON: options })` handle this conversion automatically.

### Example code: create credential creation options

We're using the [SimpleWebAuthn library](https://developers.google.com/identity/passkeys/developer-guides/server-registration#about_the_example_code) in our examples. Here, we hand over the creation of public key credential options to its [`generateRegistrationOptions`](https://simplewebauthn.dev/docs/packages/server#1-generate-registration-options) function.

> [!NOTE]
> **Note:** Operations such as `challenge` creation are abstracted away, and live in SimpleWebAuthn's code. If you're curious, check SimpleWebAuthn's [source code for `generateRegistrationOptions`](https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/registration/generateRegistrationOptions.ts).

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

    router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
      const { user } = res.locals;
      // 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 {
        // `excludeCredentials` prevents users from re-registering existing
        // credentials for a given passkey provider
        const excludeCredentials = [];
        const credentials = Credentials.findByUserId(user.id);
        if (credentials.length > 0) {
          for (const cred of credentials) {
            excludeCredentials.push({
              id: cred.id,
              type: 'public-key',
              transports: cred.transports,
            });
          }
        }

        // Generate registration options for WebAuthn create
        const options = await generateRegistrationOptions({
          rpName: process.env.RP_NAME,
          rpID: process.env.HOSTNAME,
          userID: isoUint8Array.fromUTF8String(user.id),
          userName: user.username,
          userDisplayName: user.displayName || '',
          attestationType: 'none',
          excludeCredentials,
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            residentKey: 'required',
            userVerification: 'preferred',
          },
        });

        // Keep the challenge in the session
        req.session.challenge = options.challenge;

        return res.json(options);
      } catch (e) {
        console.error(e);
        return res.status(400).send({ error: e.message });
      }
    });

## Store the public key

![PublicKeyCredentialCreationOptions sent by the server](https://developers.google.com/identity/passkeys/developer-guides/images/server-publicKeyCredentialCreationOptions.png) `navigator.credentials.create` returns a `https://www.w3.org/TR/webauthn#publickeycredential` object.

When `navigator.credentials.create` resolves successfully on the client, it means a passkey has been successfully created. A [`PublicKeyCredential`](https://www.w3.org/TR/webauthn/#iface-pkcredential) object is returned.

> [!NOTE]
> **Note:** In WebAuthn Level 3, [`PublicKeyCredential.prototype.toJSON()`](https://w3c.github.io/webauthn/#dom-publickeycredential-tojson) converts the credential response into a JSON-compatible object with Base64URL-encoded strings. When using `@simplewebauthn/browser`, `startRegistration()` automatically returns this JSON object (`RegistrationResponseJSON`) so it can be sent directly to the server without manual encoding.

The `PublicKeyCredential` object contains an `AuthenticatorAttestationResponse` object, which represents the passkey provider's response to the client's instruction to create a passkey. It contains information about the new credential that you need as an RP to authenticate the user later. Learn more about `AuthenticatorAttestationResponse` in [Appendix: `AuthenticatorAttestationResponse`](https://developers.google.com/identity/passkeys/developer-guides/server-registration#appendix_authenticatorattestationresponse).

Send the `PublicKeyCredential` object to the server. Once you've received it, verify it.

Hand over this verification step to your FIDO server-side library. It will typically offer a utility function for this purpose. SimpleWebAuthn offers, for example, [`verifyRegistrationResponse`](https://simplewebauthn.dev/docs/packages/server#2-verify-registration-response). Learn what's happening under the hood in [Appendix: verification of the registration response](https://developers.google.com/identity/passkeys/developer-guides/server-registration#appendix_verification_of_the_registration_response).

Once verification is successful, store credential information in your database so the user can later authenticate with the passkey associated with that credential.

> [!IMPORTANT]
> **Important:** Review the [example code for credential verification and storage](https://developers.google.com/identity/passkeys/developer-guides/server-registration#example_code_store_the_credential).

Use a dedicated table for public key credentials associated with passkeys. A user can only have a single password, but can have multiple passkeys --- for example, a passkey synchronized via Apple iCloud Keychain and one via Google Password Manager.

Here's an example schema you can use to store credential information:

![Database schema for passkeys](https://developers.google.com/identity/passkeys/developer-guides/images/database-schema-passkeys.png)

- **Users** table:
  - **`user_id`** : The primary user ID. A random, unique, permanent ID for the user. Use this as a primary key for your **Users** table.
  - **`username`**. A user-defined username, potentially editable.
  - **`passkey_user_id`** : The passkey-specific PII-free user ID, represented by `user.id` in your [registration options](https://developers.google.com/identity/passkeys/developer-guides/server-registration#create_credential_creation_options). When the user later attempts to authenticate, the authenticator will make this`passkey_user_id` available in its authentication response in `userHandle`. We recommend you don't set `passkey_user_id` as a primary key. Primary keys tend to become de facto PII in systems, because they're extensively used.
- **Public key credentials** table:
  - **`id`** : Credential ID. Use this as a primary key for your **Public key credentials** table.
  - **`public_key`**: Public key of the credential.
  - **`passkey_user_id`** : Use this as a foreign key to establish a link with the **Users** table.
  - **`backed_up`** : A passkey is [backed up](https://w3c.github.io/webauthn/#backed-up) if it's synchronized by the passkey provider. Storing the [backup state](https://w3c.github.io/webauthn/#backed-up) is useful if you want to consider dropping passwords in the future for users who hold `backed_up` passkeys. You can check whether the passkey is backed up by examining the BE flag in `authenticatorData`, or by using a FIDO server-side library feature that's typically available to give you easy access to this information. Storing the [backup eligibility](https://w3c.github.io/webauthn/#backup-eligible) can be helpful to address potential user inquiries.
  - **`name`**: Optionally, a display name for the credential to enable users to give credentials custom names.
  - **`transports`** : An array of [transports](https://www.w3.org/TR/webauthn/#dom-publickeycredentialdescriptor-transports). Storing transports is useful for the authentication user experience. When transports are available, the browser can behave accordingly and display a UI that matches the transport the passkey provider uses for communicating with clients---in particular for re-authentication use cases where `allowCredentials` is not empty.

Other information can be helpful to store for user experience purposes, including items like the passkey provider, the credential creation time and the last used time. Read more in [Passkeys user interface design](https://developers.google.com/identity/passkeys/ux/user-interface-design#display_%E2%80%9Cpasskeys_cards%E2%80%9D_within_account_settings).

### Example code: store the credential

We're using the [SimpleWebAuthn library](https://developers.google.com/identity/passkeys/developer-guides/server-registration#about_the_example_code) in our examples.
Here, we hand over the registration response verification to its [`verifyRegistrationResponse`](https://simplewebauthn.dev/docs/packages/server#2-verify-registration-response) function.

> [!NOTE]
> **Note:** If you're curious, check SimpleWebAuthn's [source code for `verifyRegistrationResponse`](https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/registration/verifyRegistrationResponse.ts) or dive into the complete list of verifications in the [specification](https://www.w3.org/TR/webauthn/#sctn-registering-a-new-credential).

    import { isoBase64URL } from '@simplewebauthn/server/helpers';


    router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
      const expectedChallenge = req.session.challenge;
      const expectedOrigin = getOrigin(req.get('User-Agent'));
      const expectedRPID = process.env.HOSTNAME;
      const response = req.body;
      // This sample code is for registering a passkey for an existing,
      // signed-in user

      // 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 {
        // Verify the credential
        const { verified, registrationInfo } = await verifyRegistrationResponse({
          response,
          expectedChallenge,
          expectedOrigin,
          expectedRPID,
          requireUserVerification: false,
        });

        if (!verified) {
          throw new Error('Verification failed.');
        }

        const {
          aaguid,
          credential,
          credentialBackedUp
        } = registrationInfo;

        // Name the credential based on AAGUID
        const name =
          aaguid === undefined ||
          aaguid === '00000000-0000-0000-0000-000000000000' ?
            req.useragent?.platform : aaguids[aaguid]?.name;

        const base64PublicKey = isoBase64URL.fromBuffer(credential.publicKey);

        // Existing, signed-in user
        const { user } = res.locals;

        // Save the credential
        await Credentials.update({
          id: credential.id,
          passkey_user_id: user.passkey_user_id,
          publicKey: base64PublicKey,
          name,
          aaguid,
          transports: credential.transports || response.response.transports,
          backed_up: credentialBackedUp,
          registered_at: new Date().getTime()
        });

        // Kill the challenge for this session
        delete req.session.challenge;

        return res.json(user);
      } catch (e) {
        delete req.session.challenge;

        console.error(e);
        return res.status(400).send({ error: e.message });
      }
    });

## Appendix: `AuthenticatorAttestationResponse`

`AuthenticatorAttestationResponse` contains two important objects:

- `response.clientDataJSON` is a JSON version of [client data](https://w3c.github.io/webauthn/#dictdef-collectedclientdata), which on the web is data as seen by the browser. It contains the RP origin, the challenge and `androidPackageName` if the client is an Android app. As an RP, reading `clientDataJSON`gives you access to information the browser saw at the time of the `create` request.
- `response.attestationObject`contains two pieces of information:
  - `attestationStatement` which is not relevant unless you use attestation.
  - `authenticatorData` is data as seen by the passkey provider. As an RP, reading `authenticatorData`gives you access to the data seen by the passkey provider and returned at the time of the `create` request.

`authenticatorData`contains essential information about the public key credential that's associated with the newly-created passkey:

- The public key credential itself, and a unique credential ID for it.
- The RP ID associated with the credential.
- Flags that describe user status when the passkey was created: whether a user was actually present, and whether the user was successfully verified (see [userVerification deep dive](https://web.dev/articles/webauthn-user-verification)).
- AAGUID is an identifier for the passkey provider such as Google Password Manager. Based on the AAGUID, you can identify the passkey provider and display the name in [a passkey management page](https://web.dev/articles/passkey-management). (see [Determine the passkey provider with AAGUID](https://web.dev/articles/webauthn-aaguid))

Even though `authenticatorData` is nested within `attestationObject`, the information it contains is needed for your passkey implementation whether or not you use attestation. `authenticatorData`is encoded, and contains fields that are encoded in a binary format. Your server-side library will typically handle parsing and decoding. If you're not using a server-side library, consider leveraging `getAuthenticatorData()` client-side to save yourself some parsing and decoding work server-side.

## Appendix: verification of the registration response

Under the hood, verifying the registration response consists of the following checks:

- Ensure that the RP ID matches your site.
- Ensure that the request's origin is an expected origin for your site (main site URL, Android app).
- If you require user verification, ensure that the user verification flag `authenticatorData.uv` is `true`.
- The user presence flag `authenticatorData.up` is usually expected to be `true`, but if the credential is [conditionally created](https://web.dev/articles/webauthn-conditional-creation), it's expected to be `false`.
- Check that the client was able to provide the challenge you gave it. If you don't use attestation, this check is unimportant. However, implementing this check is a best practice: it ensures your code is ready if you decide to use attestation in the future.
- Ensure that the credential ID is not yet registered for any user.
- Verify that the algorithm used by the passkey provider to create the credential is an algorithm you listed (in each `alg` field of `publicKeyCredentialCreationOptions.pubKeyCredParams`, which is typically defined within your server-side library and not visible from you). This ensures that users can only register with algorithms that you have chosen to allow.

To learn more, check SimpleWebAuthn's [source code for `verifyRegistrationResponse`](https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/registration/verifyRegistrationResponse.ts) or dive into the complete list of verifications in the [specification](https://www.w3.org/TR/webauthn/#sctn-registering-a-new-credential).

## Next up

[Server-side passkey authentication](https://developers.google.com/identity/passkeys/developer-guides/server-authentication)