We recommend only enrolling passkeys after users have already enrolled with another authentication factor (like email OTP, SMS, TOTP, etc.).

Enable and configure passkeys in the Authsignal Portal

  1. Navigate to the Authenticators section and click Set up Passkey.

  2. Add the Relying Party ID in the next step. It’s the domain where your app is hosted (e.g. example.com). Then click Activate passkeys.

    For local development, we recommend creating a separate tenant. Add localhost as the Relying Party ID.

  3. Set the expected origins on the next screen.

Add the expected origins for any subdomains you’re using (e.g. https://auth.example.com).

Defining the expected origins for your domain

For local development, it will be your localhost app url, e.g. http://localhost:3000.

Creating a passkey

1. Backend - Generate enrollment token

In your app’s backend, track an action using Server SDKs or with a REST call to our Server API.

// Track action to generate token for passkey enrollment
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "enroll-passkey",
  attributes: {
    scope: "add:authenticators",
  },
};

const response = await authsignal.track(request);
const token = response.token; // Pass this to frontend

When tracking an action to create a passkey, the scope add:authenticators must be specified if the user is already enrolled with an authenticator - this includes another passkey.

In such cases you should ensure users are strongly authenticated with an existing method before creating the passkey. Learn more about authenticator binding.

2. Frontend - Create the passkey

In your app’s frontend, call the signUp function in one of our Client SDKs, passing the token returned in step 1.

const { data, error } = await authsignal.passkey.signUp({ 
  token: tokenFromBackend, // Token from step 1
  username: user.email,    // User's email or username
});

if (error) {
  console.error("Passkey enrollment failed:", error);
} else {
  console.log("Passkey enrolled successfully");
  // Continue with your app flow
}

Authenticating with passkeys

1. Frontend - Sign in with passkey

In your app’s frontend, call the signIn function in one of our Client SDKs:

const response = await authsignal.passkey.signIn({
  action: "signInWithPasskey",
});

if (response.data?.token) {
  // Send token to your backend for validation
  const token = response.data.token;
} else {
  console.error("Passkey sign-in failed:", response.error);
}

Check out our best practice guides for web browsers and native mobile apps for tips on how to implement an optimal passkey UX and avoid leading users into dead ends.

2. Backend - Validate authentication

Pass the token returned by the Client SDK in step 1 to your backend, validating the result of the authentication server-side.

const response = await authsignal.validateChallenge({
  token: tokenFromFrontend,
});

if (response.state === "CHALLENGE_SUCCEEDED") {
  // User successfully authenticated with passkey
  const userId = response.userId;
} else {
  // Authentication failed
  console.error("Passkey validation failed");
}

Using autofill (Web and iOS only)

This requires you to have an input field on your web page or app screen for the identifier (e.g. email address) which is used to login. When the input field is focused, the user will be able to select an existing passkey if one is available on their device.

<input placeholder="Email address" autocomplete="username webauthn" />

1. Frontend - Enable passkey autofill

In your app’s frontend, call the signIn function in one of our Client SDKs and set the autofill param to true.

authsignal.passkey.signIn({ 
  action: "signInWithPasskey",
  autofill: true 
}).then((response) => {
  if (response.data?.token) {
    // User selected a passkey via autofill
    // Send token to your backend for validation
  }
});

If the user focuses the input field and successfully activates their passkey, the Authsignal Client SDK will resolve with a token.

On Android you can achieve a similar UX by showing an input field and calling signIn() when the field is focused.

2. Backend - Validate result

Send the token returned by the Client SDK to your backend and validate the result of the sign-in attempt server-side.

const response = await authsignal.validateChallenge({
  token: tokenFromFrontend,
});

if (response.state === "CHALLENGE_SUCCEEDED") {
  // User successfully authenticated with passkey
  const userId = response.userId;
} else {
  // Authentication failed
  console.error("Passkey validation failed");
}

Set your Relying Party ID to your production domain

You can run your web app locally on your production domain (e.g. example.com) instead of using localhost - e.g. by editing your hosts file. You will likely also need to use a self-signed development certificate to run your app locally on an https endpoint.