Passkeys API¶
FIDO2/WebAuthn passkey endpoints allow users to register and authenticate using biometrics or security keys.
Overview¶
Passkey flows use a challenge-response protocol with two phases each:
| Flow | Phase 1 (Begin) | Phase 2 (Finish) |
|---|---|---|
| Registration | POST /api/auth/passkey/register/begin |
POST /api/auth/passkey/register/finish |
| Login | POST /api/auth/passkey/login/begin |
POST /api/auth/passkey/login/finish |
Registration Flow¶
Users must be logged in to register a passkey (it's linked to their existing account).
Step 1: Begin Registration¶
Headers: Authorization: Bearer <accessToken>
Response (200):
{
"publicKey": {
"challenge": "base64url-encoded-challenge",
"rp": {
"name": "Bedrud",
"id": "meet.example.com"
},
"user": {
"id": "base64url-encoded-user-id",
"name": "user@example.com",
"displayName": "John Doe"
},
"pubKeyCredParams": [
{ "type": "public-key", "alg": -7 },
{ "type": "public-key", "alg": -257 }
],
"authenticatorSelection": {
"userVerification": "preferred"
}
}
}
Pass this to the browser's WebAuthn API:
Step 2: Finish Registration¶
Headers: Authorization: Bearer <accessToken>
Request Body: The credential response from the browser, base64url-encoded:
{
"id": "credential-id",
"rawId": "base64url-raw-id",
"response": {
"attestationObject": "base64url-attestation",
"clientDataJSON": "base64url-client-data"
},
"type": "public-key"
}
Response (200):
Login Flow¶
Step 1: Begin Login¶
No authentication required.
Response (200):
{
"publicKey": {
"challenge": "base64url-encoded-challenge",
"rpId": "meet.example.com",
"userVerification": "preferred"
}
}
Pass this to the browser's WebAuthn API:
Step 2: Finish Login¶
Request Body: The assertion response from the browser:
{
"id": "credential-id",
"rawId": "base64url-raw-id",
"response": {
"authenticatorData": "base64url-auth-data",
"clientDataJSON": "base64url-client-data",
"signature": "base64url-signature"
},
"type": "public-key"
}
Response (200):
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe"
}
}
Security¶
| Measure | Description |
|---|---|
| Challenge/Response | Server-generated random challenges stored in session |
| Origin Verification | Strict origin and Relying Party ID validation |
| Counter Validation | Protects against cloned authenticators |
| Secure Transport | Designed for HTTPS with URL-safe base64 encoding |
Implementation Details¶
- Backend: Uses
go-passkeys/go-passkeysfor pure Go WebAuthn verification - Session Storage: Challenges are stored in Gorilla sessions (via
gothic.Store) - Database: Passkey credentials stored in the
Passkeymodel (credential ID, public key, counter) - Frontend (Web):
navigator.credentialsAPI with base64url helpers insrc/lib/auth.ts - Android: Credentials API for native passkey support
- iOS: ASAuthorizationController for passkey support
How to Test¶
- Log in with an existing account
- Click the passkey registration button (fingerprint icon in the header/dashboard)
- Complete the biometric prompt
- Log out
- Click "Sign in with Passkey" on the login page
- Complete the biometric prompt to authenticate