Skip to main content
Passkeys (WebAuthn) let people sign in to the Yativo dashboard without a password or email code. The device signs a one-time challenge with a private key held by Face ID, Touch ID, Windows Hello, a phone, or a hardware security key. Yativo checks the signature and returns a bearer token for the dashboard. This guide is for developers building the dashboard sign-in and passkey settings screens.
Passkeys are for people, not servers. The token a passkey returns only works inside the dashboard (since 15 October 2026). Backends and scripts must use a team API key instead. The old account_id + app_secret login is deprecated. See Move from login tokens to API keys.


Overview

Passkeys follow the WebAuthn Level 2 standard. There are two flows: A user has to sign in once another way (email one-time code) before they can register a passkey. Every person on a team registers their own passkeys and signs in as themselves: the team owner and each invited member have separate passkeys, and a member’s passkey signs in to that member’s own account. Dashboard sign-in options today:

Environments

All paths below include /api/v1. Passkeys are stored per environment, so a passkey registered in sandbox doesn’t work in production.

Where passkeys work

The relying party ID (rp.id / rpId) is yativo.com. Browsers only allow a WebAuthn ceremony for that ID on a page served from yativo.com or one of its subdomains, such as https://app.yativo.com.
  • Run the browser part (navigator.credentials.create() / .get()) on a *.yativo.com page. On any other origin, including localhost, the browser rejects it with a SecurityError.
  • A passkey registered from one Yativo subdomain works on the others.

Authentication

  • Registration and management endpoints need the signed-in person’s bearer token, sent from the dashboard. They act on that person’s own passkeys, whatever their team role. They can’t be called with an API key (403 This endpoint cannot be used with an API key. ...).
  • Sign-in endpoints (/auth/passkeys/authenticate/options and /auth/passkeys/authenticate) are public.
  • Send cookies. The challenge from each “options” call is kept in the session, so the options call and the call that follows it must share the session cookie. With fetch, set credentials: 'include'.

Response envelope

Every endpoint returns the standard envelope. Success responses come back with HTTP 200, even when the body says "status_code": 201, so check status in the body. Errors come back with their real HTTP status.

Registration flow

1. Get registration options

No body is needed.
  • Allowed algorithms: ES256 (-7), ES512 (-36), RS256 (-257), EdDSA (-8) and ES384 (-35).
  • userVerification is required: the device always asks for Face ID, a fingerprint, or a PIN, not just a tap.
  • excludeCredentials lists the user’s existing passkeys so the same device isn’t registered twice.

2. Create the credential in the browser

3. Save the credential


Sign-in flow

1. Get sign-in options

account_id is the user’s email, base64-encoded (standard base64, not base64url).

2. Sign the challenge in the browser

3. Send the signed challenge

expires_in is the token lifetime in seconds. Use the value returned rather than hard-coding it.

Using the token

The passkey token is the same kind of token as the email one-time code login, and the same rules apply:
  • Send it as Authorization: Bearer <access_token>.
  • It only works from the dashboard. Since 15 October 2026, a request with a bearer token whose Origin (or Referer) isn’t https://app.yativo.com gets 403 Bearer tokens are only accepted from the Yativo dashboard. ...
  • It expires after 5 minutes without a request, even before expires_in, with 401 Session expired due to inactivity. Please log in again. Send the user back to sign in.
  • A team member’s token acts on the team’s account, limited by their role. See Team Functionality.

Confirming a sensitive action

Account-recovery actions ask the signed-in person to confirm with one of their passkeys: resetting 2FA and resetting the transaction PIN. This is a “step-up” check, not a sign-in.
  1. Get a challenge for the signed-in person (bearer token required):
    The response has the same public_key shape as sign-in options. It returns 422 No passkeys registered for this account. when the person has no passkey. Limited to 10 requests per minute.
  2. Sign it in the browser with navigator.credentials.get(), exactly as in sign-in step 2.
  3. Send the signed result with the recovery request, nested under passkey:
Each challenge works once and expires after timeout milliseconds (60 seconds). If the request fails, get a new challenge before retrying.

Passkey management

All management endpoints need the signed-in user’s bearer token.

List passkeys

Rename a passkey

Delete a passkey


Errors

Challenges expire after 60 seconds and can only be used once. Request new options right before prompting the user.

Frontend example

Check window.PublicKeyCredential before showing passkey options, and always keep email code sign-in available as a fallback.

Security notes

  • Challenges are single-use and expire after 60 seconds.
  • Origin binding: passkeys are bound to yativo.com, so they can’t be used on phishing sites on other domains.
  • Several passkeys: each person can register one per device. Encourage clear names and removing passkeys for devices they no longer have.