Skip to content

WebSocket API

The IDToken Auth Server uses WebSocket to deliver real-time authentication session updates to the browser. This is how the browser knows when the user has confirmed (or rejected) the OTP on their mobile app.

GET /ws/session/{sessionId}?token={wsToken}
ParameterSourceDescription
sessionIdPOST /auth/initiate responseThe active session identifier
wsTokenPOST /auth/initiate responseHMAC-SHA256 authentication token

The wsToken is verified using constant-time HMAC-SHA256 comparison, preventing timing attacks on the WebSocket authentication.

  • One connection per session — if a new WebSocket connects for the same session, the previous connection is closed with code 4009
  • Session must exist — the sessionId must be present in Redis
  • Auto-cleanup — connection is automatically closed when the session expires or is resolved

All events are JSON messages sent from the server to the client.

Sent immediately after the session is initiated. Contains the OTP for display to the user.

{
"type": "otp_ready",
"autoPassword": "4829**",
"expiresAt": "2025-01-15T10:01:00Z"
}

The autoPassword may be partially masked depending on the server configuration.

Sent when the mobile app successfully verifies the OTP and signature.

{
"type": "approved",
"jwt": "eyJhbGciOiJFUzI1NiIs...",
"hash": "hmac-response-verification",
"random": "a1b2c3d4e5f67890",
"expiresAt": "2025-01-15T11:00:00Z"
}
FieldDescription
jwtThe signed JWT containing identity claims
hashResponse verification HMAC for anti-forgery
randomRandom nonce from initiation (for hash verification)
expiresAtJWT expiration time

Sent when verification fails.

{
"type": "rejected",
"reason": "Invalid signature"
}
ReasonDescription
Invalid signatureECDSA signature verification failed
Invalid OTPOTP mismatch (outside tolerance window)
Too many attemptsBrute-force limit exceeded (3 attempts)
CodeMeaning
1000Normal closure (session resolved)
4001Invalid wsToken
4004Session not found
4009Replaced by newer connection
function connectSession(sessionId, wsToken) {
const ws = new WebSocket(
`wss://auth.idtoken.example.com/ws/session/${sessionId}?token=${wsToken}`
);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'otp_ready':
displayOTP(data.autoPassword);
break;
case 'approved':
handleSuccess(data.jwt, data.hash, data.random);
ws.close();
break;
case 'rejected':
handleRejection(data.reason);
ws.close();
break;
}
};
ws.onclose = (event) => {
if (event.code === 4009) {
console.log('Connection replaced by another tab');
}
};
return ws;
}