Authentication Flow
IDToken implements the ITU-T X.1280 mutual out-of-band authentication protocol. The service presents an OTP to the user, who confirms it on their mobile device with facial recognition.
Protocol Overview
Section titled “Protocol Overview”The X.1280 protocol inverts traditional authentication: instead of the user proving their identity to the service, both parties authenticate each other through an out-of-band channel.
Phase 1: Initiation
Section titled “Phase 1: Initiation”The browser (or relying party) initiates an auth session by providing the user’s tokenId and identifying the service.
Request: POST /auth/initiate
The service can identify itself in two ways:
Option A — Service VDS (recommended): The service presents its cryptographically signed Service VDS. The server verifies the signature and extracts the service identity — no serviceId needed. See Mutual Service Identity for details.
{ "tokenId": "vds-uuid-from-enrollment", "serviceVds": "HC1:6BFOXN%TS3DH...", "scopes": ["identity:name", "identity:age_over_18"]}Option B — serviceId (legacy): The service passes a plain identifier registered by an administrator.
{ "tokenId": "vds-uuid-from-enrollment", "serviceId": "my-web-app", "scopes": ["identity:name", "identity:age_over_18"]}Server processing:
- Verify enrollment exists and is not revoked
- Identify the service: verify Service VDS signature (Option A) or look up serviceId in registry (Option B)
- Validate requested scopes against the service’s allowed scopes (from VDS scope ceiling or registry)
- Generate a 6-digit OTP using HKDF-SHA256, bound to the session context (tokenId + sessionId)
- Generate a WebSocket authentication token (HMAC-SHA256)
- Generate a random nonce for response verification
- Store session in the ephemeral cache (60-second TTL)
- Send FCM push notification to the mobile app (with verified service identity when using Service VDS)
- Notify the browser via WebSocket
Response:
{ "sessionId": "sess_abc123", "autoPassword": "482917", "wsToken": "hmac-token-for-websocket", "random": "a1b2c3d4e5f6...", "expiresAt": "2025-01-15T10:01:00Z"}Phase 2: Browser Connects WebSocket
Section titled “Phase 2: Browser Connects WebSocket”The browser connects to the WebSocket endpoint to receive real-time session updates.
Connection: GET /ws/session/{sessionId}?token={wsToken}
The wsToken is verified using constant-time HMAC comparison. Only one connection per session is allowed (new connections replace old ones with close code 4009).
Events received:
{ "type": "otp_ready", "autoPassword": "4829**", "expiresAt": "..." }The browser displays the OTP to the user.
Phase 3: Mobile App Confirms
Section titled “Phase 3: Mobile App Confirms”The mobile app receives an FCM push notification containing the OTP and session details. The user:
- Compares the OTP on screen with the OTP on their phone
- If they match, confirms with facial recognition (id3 Face SDK)
- The app signs the verification payload with its ECDSA private key
Signed message format:
{sessionId}|{otp}|{timestamp}[|{grantedScopes}]The grantedScopes field is optional and allows the user to selectively grant a subset of the requested scopes.
Phase 4: Server Verification
Section titled “Phase 4: Server Verification”The mobile app sends the signed verification to the server.
Request: POST /auth/verify
{ "sessionId": "sess_abc123", "tokenId": "vds-uuid", "otp": "482917", "signatureBase64": "MEUCIQD...", "timestamp": 1705312860, "grantedScopes": ["identity:name", "identity:age_over_18"]}Server processing:
- Retrieve session
- Validate timestamp (must be within ±30 seconds)
- Check attempt counter (max 3 attempts)
- Verify ECDSA signature against the enrollment’s public key
- Verify OTP (±1 time window tolerance)
- Build JWT claims based on granted scopes and VDS data
- Compute response verification hash (HMAC-SHA256 over jwt, sessionId, and nonce)
- Notify the browser via WebSocket
- Log
AUTH_APPROVEaudit event - Delete session (single-use)
Response:
{ "jwt": "eyJhbGciOiJFUzI1NiIs...", "hash": "response-verification-hmac", "random": "a1b2c3d4e5f6...", "expiresAt": "2025-01-15T11:00:00Z"}Phase 5: Browser Receives JWT
Section titled “Phase 5: Browser Receives JWT”The browser receives the JWT via WebSocket:
{ "type": "approved", "jwt": "eyJhbGciOiJFUzI1NiIs...", "hash": "response-verification-hmac", "random": "a1b2c3d4e5f6...", "expiresAt": "2025-01-15T11:00:00Z"}The browser can optionally verify the response hash to confirm the JWT came from the legitimate server (X.1280 anti-forgery protection).
JWT Claims Structure
Section titled “JWT Claims Structure”The issued JWT contains scope-filtered identity claims:
{ "iss": "https://idtoken.example.com", "aud": "https://services.example.com", "sub": "vds-token-id", "iat": 1705312800, "exp": 1705316400, "scope": "identity:name identity:age_over_18", "idtoken": { "tokenId": "vds-token-id", "givenName": "Jean", "familyName": "Dupont", "ageOver18": true, "trustLevel": 3 }}See Claims & Scopes for the full scope system.
Rejection Flow
Section titled “Rejection Flow”If verification fails, the server publishes a rejected event:
{ "type": "rejected", "reason": "Invalid signature"}Possible rejection reasons:
- Invalid signature — ECDSA signature verification failed
- Invalid OTP — OTP does not match (within tolerance)
- Too many attempts — Brute-force limit exceeded (3 attempts)
- Session expired — 60-second TTL exceeded
Security Properties
Section titled “Security Properties”| Property | Mechanism |
|---|---|
| Mutual authentication | User verifies service via OTP match (and verified Service VDS identity); service verifies user via ECDSA signature |
| Replay protection | Sessions are single-use, deleted after verification |
| Brute-force protection | Max 3 OTP attempts per session |
| Time-bound | 60-second session TTL, ±30s timestamp validation |
| Anti-forgery | Response hash allows browser to verify JWT authenticity |
| Out-of-band | OTP delivered via separate channel (FCM push) |