Users and credentials
FIDO / passkeys API
The FIDO2 server of a tenant. It runs the passkey registration and sign-in ceremonies, stores each user's authenticators (passkeys) and holds the authenticator policies that decide which passkeys are accepted.
https://{tenant-host}/fido2audience fido2_server17 operationsWhen to use it
- List, rename, suspend or delete a user's passkeys from a backend or an admin screen.
- Delete a user's passkeys before you delete the user, so the passkey can't sign in any more.
- Read or change the authenticator policy: user verification, discoverable credentials, allowed authenticators.
- Get the registration or sign-in options for a WebAuthn ceremony, and verify the browser's answer.
Connect
- Base URL
https://{tenant-host}/fido2- Token
POST https://{tenant-host}/oauth/v1/tokenwithgrant_type=client_credentials. The client ID and secret go in the form body. Send the token asAuthorization: Bearer.checked live- Client to create
- Access admin → Web clients → a client with the grant type Client credentials. Not an API client: those are only for Access's own configuration API.checked live
- Default audiences
- must include
fido2_server. Notfido2: no tenant we checked uses that name.checked live - Default scopes
- One
fido2_server-<operation>scope per operation, listed below. Access defines 16 of them, e.g.fido2_server-policies_list,fido2_server-authenticators_list. Keep the client's scopes, joined with spaces, within 1024 characters.checked live - Not API keys or mTLS
- Older references name
API_KEYandMTLSschemes and a demo server. On a tenant you callhttps://{tenant-host}/fido2/...with the bearer token above.checked live
curl -s https://{tenant-host}/oauth/v1/token \
-d grant_type=client_credentials \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
curl -s https://{tenant-host}/fido2/policies \
-H "Authorization: Bearer $ACCESS_TOKEN"Operations
Open an operation to see its parameters and code, and send it to your tenant. Each operation checks its own scope. The scope names come from the reference and all exist in Access; policy reads were run on a tenant.
Connect to your tenant
Then open any operation below and send it.Sends grant_type=client_credentials to https://{tenant-host}/oauth/v1/token through this site's server. The secret and token stay in this tab only; host and client ID are remembered.
What trips people up
A user with no passkeys is a 404, not an empty list
checked liveGET /authenticators?userId=... answers 404 with {"status":"failed","errorMessage":"No authenticators are found for userId '...'"}. Treat that as "none", not as an error. The authenticators key is absent, not empty.
A policy name that matches nothing is also a 404
checked liveGET /policies?name=x answers 404 "No policies are found for tenant [...], policy name [x]" instead of an empty policies array. An unknown policyId is a 404 too.
Read status as well as the HTTP code
checked liveEvery reply carries status (ok, failed or warn). Errors add errorMessage and sometimes errorRef. Some error messages include the tenant's internal ID: log them, don't show them to end users.
The audience is fido2_server
checked liveNot fido2, which no tenant we checked uses. The scopes share the prefix and name the operation: fido2_server-policies_list, fido2_server-authenticators_list.
One scope is documented but not defined in Access
checked liveDeleting a device-bound key needs fido2_server-authenticator_delete_dbk. Access on the tenant we checked defines the other 16 FIDO scopes but not this one. Create it in Access before you give it to a client.
A tenant starts with a policy named default
checked liveThe platform creates it. On the tenant we checked it requires user verification and has outcome: fail. Read it before you create another one: policy names must be unique.
New policies are lenient unless you say otherwise
not confirmedOnly name is required. Fields you leave out fall back to outcome: warn, userVerification: preferred, discoverable: preferred and metadata: no. With warn, a passkey that breaks the policy is still accepted. Set outcome: fail to enforce it.
userId is the user handle given at registration
not confirmedThe server stores whatever userId the registration options carried. Flows that pass the Identity Store user ID there can list and delete passkeys with that same ID. Sign-in returns it as userId.
Bulk delete needs userId or clientId
not confirmedDELETE /authenticators takes userId, clientId or both, and at least one is required. With both, only that user's passkeys from that client device are deleted.
Deleting a user doesn't delete their passkeys
not confirmedPasskeys live here, not in the Identity Store. Delete them (DELETE /authenticators?userId=...) before you delete the user, and check the account after every passkey sign-in.
Errors
Error bodies carry status: "failed", errorMessage and sometimes errorRef (an error ID). There is no error code field. 401 and 403 come from the security layer.
| 400 | failed | Invalid parameters, e.g. a bulk delete without userId or clientId. |
| 401 | — | Token missing, expired, or without the fido2_server audience. |
| 403 | — | The token has the audience but not the operation's fido2_server-... scope. |
| 404 | failed | Nothing found: unknown policy or passkey, a user with no passkeys, or a policy name that matches nothing. |
| 409 | failed | Conflict, e.g. a policy name that already exists, or a registration or sign-in that can't be completed. |
| 500 | failed | Unexpected failure. Quote errorRef when you report it. |
When a call fails
| Reply | Means | What to change |
|---|---|---|
| The service answers 401 | The token lacks this service's audience, or it expired. | Add the audience to the client's Default audiences (Access admin → Web clients → the client), then get a new token. |
| The service answers 403 and the token has the audience | A scope the operation needs is missing. | Add one of the operation's scopes to the client's Default scopes. Use the scope names your tenant lists under Access admin → Scopes. |
| Token endpoint: 401 invalid_client | Unknown client ID or wrong secret. | Check the client ID and paste the secret again. |
| Token endpoint: 400 unauthorized_client | The client may not use client credentials. | Add the grant type Client credentials to the web client. |
| Token endpoint: 400 invalid_request about an audience | You asked for an audience the client doesn't have. | Add it to Default audiences, or stop asking for it. |
| Token endpoint: 500 right after scopes were added | The client's Default scopes, joined with spaces, are over about 1024 characters. | Move scopes to Additional scopes and request them per call, or split the work over two clients. |
| {"code":"not_found","message":"The requested location was not found"} | That service isn't deployed on this tenant. | Nothing to change in the client. Ask whoever runs the tenant. |
Two kinds of client exist in Access. Service APIs like this one take a web client with the Client credentials grant. Access's own configuration API takes an API client: a web client sent there gets 401 invalid_credentials.