POST /v1/users/me/passkeys/register/begin
Start a WebAuthn enrollment ceremony. Returns the
PublicKeyCredentialCreationOptions that the browser hands to
navigator.credentials.create().
::: tip Auth Required: cookie or Bearer. :::
Request
POST /v1/users/me/passkeys/register/begin
| Header | Required | Notes |
|---|---|---|
Cookie: identsphere_at=... OR Authorization: Bearer ... | yes | — |
No request body.
Response
200 OK
A WebAuthn PublicKeyCredentialCreationOptions JSON object (shape as defined
by the W3C WebAuthn spec). Example abbreviated:
{
"publicKey": {
"rp": { "id": "app.example.com", "name": "Acme" },
"user": { "id": "...", "name": "alice@example.com", "displayName": "Alice" },
"challenge": "...",
"pubKeyCredParams": [{ "type": "public-key", "alg": -7 }],
"timeout": 60000,
"attestation": "none",
"excludeCredentials": [{ "type": "public-key", "id": "..." }]
}
}
Error responses
| Status | Code | When |
|---|---|---|
| 401 | authentication_required | No valid auth credential. |
| 404 | not_found | User no longer exists. |
| 409 | conflict | User already has the maximum 10 passkeys enrolled. |
| 500 | internal_error | WebAuthn ceremony build failure or DB error. |
Example: TypeScript (@identsphere/react)
import { usePasskeyEnroll } from '@identsphere/react';
function AddPasskeyButton() {
const enroll = usePasskeyEnroll();
return (
<button onClick={() => enroll.mutateAsync({ label: 'MacBook Touch ID' })}>
Add a passkey
</button>
);
}
The hook handles begin → navigator.credentials.create() → complete in
one call. If you're rolling your own client, see the manual flow below.
Example: Manual (browser)
const begin = await fetch('/v1/users/me/passkeys/register/begin', {
method: 'POST',
credentials: 'include',
}).then(r => r.json());
const cred = await navigator.credentials.create({ publicKey: begin.publicKey });
await fetch('/v1/users/me/passkeys/register/complete', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: cred, label: 'MacBook Touch ID' }),
});
Notes
- Challenge state is persisted server-side in
passkey_challengeswith a 5-minute TTL. - Already-enrolled credentials for this user are listed in
excludeCredentialsso the authenticator prompts the user to add a NEW credential rather than silently re-registering an existing one. - Per-user cap is 10 enrolled passkeys.