Skip to content

Quick Start

Integrate passwordless IDToken authentication into your application as a relying party. By the end of this guide, your users will be able to authenticate with their IDToken mobile credential instead of a password.

  • An IDToken operator endpoint (e.g., https://auth.example.com)
  • A registered serviceId (obtained from the IDToken operator during onboarding)
  • The tokenId of the user you want to authenticate (provided by your application’s enrollment flow)

Before you can initiate authentication, your service must be registered with the IDToken operator. During onboarding, you will receive:

  • A serviceId identifying your application
  • A set of allowed scopes defining which identity claims your service can request
  • Default scopes that apply when no specific scopes are requested

Contact your IDToken operator to register, or see Mutual Service Identity for details on service registration with VDS credentials.

When a user wants to log in, call the /auth/initiate endpoint with their tokenId and the scopes your application needs.

const response = await fetch("https://auth.example.com/auth/initiate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tokenId: "user-token-id",
serviceId: "your-service-id",
scopes: ["identity:name", "identity:age_over_18"],
}),
});
const { sessionId, autoPassword, wsToken, expiresAt } = await response.json();

The server returns:

FieldDescription
sessionIdUnique identifier for this authentication session
autoPasswordThe one-time password to display to the user
wsTokenToken for connecting to the WebSocket to receive real-time updates
expiresAtISO 8601 timestamp when the OTP expires

The server simultaneously sends a push notification to the user’s mobile app with the same OTP, the session details, and the scopes you requested.

3. Display the OTP and Listen for Approval

Section titled “3. Display the OTP and Listen for Approval”

Display the autoPassword to the user in your browser UI. The user will see the same OTP on their mobile device and confirm it with a biometric check.

Connect to the WebSocket to receive real-time session updates:

const ws = new WebSocket(
`wss://auth.example.com/ws/session/${sessionId}?token=${wsToken}`
);
ws.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case "approved":
// Authentication succeeded
const { jwt, hash, random } = message;
handleLogin(jwt);
ws.close();
break;
case "rejected":
// User rejected or verification failed
showError(message.reason);
ws.close();
break;
}
});

When the user approves on their mobile device, the server pushes an approved message containing the signed JWT directly to your WebSocket connection. See WebSocket API for the full message reference.

The JWT is signed with ES256 (ECDSA P-256). Fetch the operator’s public keys from the JWKS endpoint and verify the signature before trusting any claims.

import * as jose from "jose";
const JWKS_URL = "https://auth.example.com/.well-known/jwks.json";
const JWKS = jose.createRemoteJWKSet(new URL(JWKS_URL));
async function verifyIdToken(jwt) {
const { payload } = await jose.jwtVerify(jwt, JWKS, {
issuer: "https://auth.example.com",
audience: "your-expected-audience",
});
return payload;
}

The JWKS endpoint supports HTTP caching (Cache-Control: public, max-age=3600), so your library will efficiently cache the keys.

The verified JWT payload contains the identity claims your service requested, scoped to what the user consented to share:

{
"sub": "user-token-id",
"iss": "https://auth.example.com",
"aud": "your-expected-audience",
"iat": 1711929600,
"exp": 1711933200,
"scope": "identity:name identity:age_over_18",
"idtoken": {
"tokenId": "user-token-id",
"givenName": "Marie",
"familyName": "Dupont",
"age_over_18": true
}
}

The idtoken object contains only the claims matching the granted scopes. Available scopes include:

ScopeClaims returned
identity:namegivenName, familyName
identity:date_of_birthdateOfBirth
identity:nationalitynationality
identity:age_over_18age_over_18 (boolean)
identity:basicComposite: identity:name + identity:trust_level
identity:fullComposite: name, date of birth, nationality, document, trust level

See Claims & Scopes for the complete scope reference and derived claims.

The full flow follows the ITU-T X.1280 mutual out-of-band authentication protocol:

  1. Your server requested authentication for a tokenId
  2. The IDToken server generated a one-time password and pushed it to both the browser (via your WebSocket) and the user’s mobile app (via push notification)
  3. The user confirmed the OTP matched and approved with a biometric check on their mobile device
  4. The mobile app cryptographically signed the approval and sent it to the IDToken server
  5. The server verified the signature, issued a JWT with the consented claims, and delivered it to your WebSocket

No password was transmitted. No user database was queried. Identity was proven cryptographically from the VDS credential on the user’s device.