Set up Passkeys with the frontend SDK

Passkeys are a biometrics-based, phishing-resistant, drop-in replacement for passwords. Stytch's Passkeys utilize local biometric verifications like FaceID or TouchID to generate asymmetric public-private keys for authentication.

In this guide, you'll learn how to set up Passkeys for login with Stytch's frontend SDK. By the end, you'll have:

  • Configured a Stytch Login component.
  • Configured a Passkey Registration component.
  • Signed up a User with Email OTP.
  • Registered a Passkey.
  • Authenticated a User with the Passkey.

Before you start

In order to complete this guide, you'll need the following:

  • A Stytch Consumer Authentication project. If you don't have one already, in the Dashboard, click on your existing project name in the top left corner of the Dashboard, click Create a new project, and then select Consumer Authentication.
  • The project Test environment's public token from the API keys section. You'll need to pass the public token into the initialized SDK client.
  • The SDK enabled. In the Dashboard, navigate to Frontend SDK under CONFIGURATION. Then click Enable SDK in Test.
  • WebAuthn enabled in the SDK settings under Authentication products.
  • A web app that serves html pages.

Passkeys require a primary factor

In order to ensure account recovery, Passkeys can only be used for login and not signup. Stytch Users are required to have a verified email or phone from primary auth factors like Magic Links, OTP, and OAuth before they can register a Passkey with Stytch's API.

Additionally, Passkey registration requires the User to have authenticated with at least two factors if possible. Please refer to the MFA SDK resource for more details.

Step 1: Install the frontend SDK

Stytch provides frontend SDK options for vanilla JavaScript, React, and Next.js. The essential steps to set up Passkeys are the same regardless of which SDK implementation you decide to use.

In this guide, we'll use the vanilla JavaScript SDK.

Install the frontend SDK locally via npm or yarn, or use unpkg to always pull the most current SDK version.

npm install @stytch/vanilla-js --save
<script type="module" src="https://www.unpkg.com/@stytch/vanilla-js"></script>

Step 2: Create a login.html page and initialize the Stytch Client

The next step is to create a login page with two elements:

  1. A <script> that initializes the Stytch Client. You'll need to pass in your Project's public token.
  2. A DOM element for the login UI component.
<!-- login.html -->
<script>
// step 2
import { StytchUIClient } from '@stytch/vanilla-js';
const stytch = new StytchUIClient('{PUBLIC_TOKEN}');
</script>

<div id="login-form"></div>

Step 3: Configure and mount the Login component

After the initializing the Stytch Client, create a config object with the necessary auth settings. Refer to the UI Config SDK reference for all available options and style customizations.

Since Passkeys cannot be used for signup, include One-Time Passcodes as an auth factor in the config object. Then mount the Login component by calling the stytch.mountLogin() method and passing in an object that has the config object and the DOM element id from Step 2.

<!-- login.html -->
<script>
// step 2
import { StytchUIClient } from '@stytch/vanilla-js';
const stytch = new StytchUIClient('{PUBLIC_TOKEN}');

// step 3
const config = {
  otpOptions: {
    expirationMinutes: 10,
    methods: ['email', 'sms']
  },
  products: [
    'otp',
    'passkeys'
  ],
};

stytch.mountLogin({
  elementId: "#login-form",
  config,
});
</script>

<div id="login-form"></div>

Step 4: Add callbacks to redirect to the home.html page

With the login form working, create a callbacks object that redirects the user upon successful authentication to a logged-in location (like a home page, dashboard, or account page). You can wire these functions to trigger on the PASSKEY_AUTHENTICATE and OTP_AUTHENTICATE events.

Refer to the UI callbacks SDK reference for a full list of events you can use for asycchronous operations.

Once defined, pass the callbacks object into stytch.mountLogin() method.

<!-- login.html -->
<script>
// step 2
import { StytchUIClient } from '@stytch/vanilla-js';
const stytch = new StytchUIClient('{PUBLIC_TOKEN}');

// step 3
const config = {
  otpOptions: {
    expirationMinutes: 10,
    methods: ['email', 'sms']
  },
  products: [
    'otp',
    'passkeys'
  ],
};

// step 4
const callbacks = {
  onEvent: ({ type, data }) => {
    if (type === 'PASSKEY_AUTHENTICATE') {
      console.log("Passkey authenticated", data);
      window.location.replace("/home");
    } else if (type === 'OTP_AUTHENTICATE') {
      console.log("OTP authenticated", data);
      window.location.replace("/home");
    } else {
      console.log(type, data)
    }
  },
  onError: (err) => {
    console.log(err);
  }
};

stytch.mountLogin({
  elementId: "#login-form",
  config,
  callbacks
});
</script>

<div id="login-form"></div>

The login.html page should render a Login component UI. The email input field should trigger an autofill behavior for Passkey credentials when clicked on. Please make sure you're using a supported browser.

Passkeys diagramPasskeys diagram

Step 5: Create a home.html page and mount the Passkey Registration component

With the login.html page redirecting to home.html upon successful authentication, the next step is to build a page where users can register Passkeys after signing up.

Create a home.html page that initializes the Stytch Client and mounts the Passkey Registration component. The Passkey Registration component accepts all the same props as the Stytch Login component from Steps 3 and 4:

  • config object.
  • target DOM element id.
  • callbacks object.

You'll need to also create a logout mechanism using the stytch.session.revoke() method.

The home.html page should have the following structure:

<!-- home.html -->
<script>
// step 5
import { StytchUIClient } from '@stytch/vanilla-js';
const stytch = new StytchUIClient('{PUBLIC_TOKEN}');

const logout = () => {
  stytch.session.revoke();
};

const config = {
  otpOptions: {
    expirationMinutes: 10,
    methods: ['email', 'sms']
  },
  products: [
    'otp',
    'passkeys'
  ],
};

const callbacks = {
  onEvent: ({ type, data }) => {
    if (type === 'PASSKEY_REGISTER') {
      console.log("Passkey registered", data);
      window.location.replace("/home");
    } else {
      console.log(type, data)
    }
  },
  onError: (err) => {
    console.log(err);
  }
};

stytch.mountPasskeyRegistration({
  elementId: "#stytch-passkey-form",
  config,
  callbacks
});


</script>

<div id="register-passkey-form"></div>

<button onclick="logout()">Log out</button>

The home.html page should render a Passkey Registration component with this UI:

Passkeys diagram

Step 6: Sign up with Email OTP

At this point, the login.html and home.html should have fully functional Login and Passkey Registration components. Now we can test the end-to-end flow for signing up and logging in to the application with Passkeys.

First, start on the login.html page and enter an email address to sign up with an Email OTP. You should receive an email with an OTP code. Complete the authentication flow and you should be redirected to home.html after receiving a 200 network response with a Session.

Passkeys diagram

Step 7: Register a Passkey

On the home.html page, click Create a Passkey on the Passkey Registration component. A browser dialog will appear with a prompt to create a Passkey with multiple options for storage such as:

  • Mobile devices
  • Tablets
  • Laptops
  • Security hardware like Yubikeys
  • iCloud
  • Chrome password manager
  • Windows Hello
  • 1Paswsword

The available options are determined by the end user's specific setup of devices, operating systems, and browsers. For example, someone who has a Macbook laptop with Chrome browser and 1Password installed may see the following dialogs appear for registering Passkeys:

Passkeys diagramPasskeys diagram

Select any option and follow all prompted steps to create a Passkey. The end result should be a 200 network response with a Session and the Passkey Registration component displaying a success message:

Passkeys diagram

Step 8: Authenticate with your Passkey

Your User record should have a verified email from signing up with OTP in Step 6 and a registered Passkey / WebAuthn factor from Step 7.

{
  "user_id": "user-test-c872...",
  "status": "active",
  "created_at": "2023-11-21T17:05:05Z",
  "emails": [
    {
      "email": "example@email.com",
      "email_id": "email-test-8c...",
      "verified": true
    }
  ],
  "webauthn_registrations": [
    {
      "authenticator_type": "",
      "domain": "localhost",
      "name": "WebAuthN Registration 531ca878",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
      "verified": true,
      "webauthn_registration_id": "webauthn-registration-test-a71f6..."
    }
  ],
  "email": "example@email.com",
  "phone_number": "",
  "name": {
    "first_name": "",
    "last_name": "",
    "middle_name": ""
  },
  "password": null,
  "phone_numbers": [],
  "providers": [],
  "totps": [],
  "trusted_metadata": {},
  "untrusted_metadata": {}
}

Log out of the home.html page and revoke the session with the logout() function created in Step 5.

The final step is to log back in by clicking the email input field which should trigger the autofill and Passkeys browser dialog.

Select the same Passkey you created in Step 7 which will succesfully authenticate into the application.

What's next

Clone the React or Next.js example app to get hands-on with a Passkeys-powered application.