Mutual Service Identity
The Problem: A Stolen serviceId Fools the User
Section titled “The Problem: A Stolen serviceId Fools the User”Today, a service identifies itself with a serviceId — a plain string. Consider what happens when an attacker obtains it:
The user’s phone shows “Bar Le Central” because that name is in the server registry — but the request came from the attacker. The serviceId proves the ID is valid, not that the caller is legitimate.
The Solution: The Service Shows Its ID Card Too
Section titled “The Solution: The Service Shows Its ID Card Too”Just as users prove their identity with a VDS (a signed credential derived from their passport), services prove their identity with a Service VDS (a signed credential from the IDToken server or a CA). At every authentication, the service includes its VDS in the request. The server verifies the signature before proceeding, and the mobile app shows the verified service identity.
How It Works
Section titled “How It Works”At authentication time, the service includes its Service VDS in the request. The server verifies the cryptographic signature before proceeding — if the VDS is forged or expired, the request is rejected at step 1.
The key change is at step 1: the service presents its Service VDS instead of a plain serviceId. The server verifies the VDS signature before proceeding, and the mobile app displays a “VERIFIED” badge with the authenticated service name.
API: Auth Initiation with Service VDS
Section titled “API: Auth Initiation with Service VDS”The POST /auth/initiate endpoint accepts the Service VDS:
{ "tokenId": "vds-uuid-from-enrollment", "serviceVds": "HC1:6BFOXN%TS3DH...", "scopes": ["identity:name", "identity:age_over_18"]}The server:
- Decodes the Base45-encoded Service VDS
- Verifies the ECDSA P-256 signature against the server’s JWKS (or CA certificate)
- Checks the VDS has not expired
- Extracts the service identity: name, URL, logo, authorized scopes
- Validates the requested scopes are within the VDS scope ceiling
- Proceeds with OTP generation and push notification — now carrying the verified service identity
When serviceVds is absent, the server falls back to serviceId-based lookup (fully backward compatible).
What the User Sees
Section titled “What the User Sees”When the service is verified via VDS, the mobile app shows a clear “VERIFIED” badge — the user knows the service identity was confirmed by cryptographic signature, not just looked up in a registry:
Security Properties
Section titled “Security Properties”| Property | Mechanism |
|---|---|
| No shared secrets | Service proves identity via signed VDS — no API keys to distribute, rotate, or leak |
| Phishing resistance | Fake services cannot forge a valid VDS signature without the signing key |
| Scope enforcement | Requested scopes validated against the cryptographically signed scope ceiling |
| Revocation | Revoke the VDS certificate to instantly block a compromised service |
| User confidence | Mobile app displays verified service identity — users know exactly who is asking |
| Backward compatible | Legacy serviceId flow continues to work alongside VDS-based identification |
Integration
Section titled “Integration”To adopt Mutual Service Identity, a relying party:
- Obtains a Service VDS — either from the IDToken Console (Model A) or a CA (Model B)
- Stores the Service VDS — the Base45-encoded VDS is a static credential (valid until expiry)
- Passes it in auth requests — include
serviceVdsinstead ofserviceIdinPOST /auth/initiate
// Frontend: initiate auth with Service VDSconst response = await fetch('https://auth.idtoken.example.com/auth/initiate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tokenId: userTokenId, serviceVds: SERVICE_VDS_BASE45, // Your Service VDS credential scopes: ['identity:name', 'identity:age_over_18'] })});
const { sessionId, autoPassword, wsToken, random } = await response.json();The rest of the flow (WebSocket connection, OTP display, JWT receipt) is identical to the standard integration. See the Integration Guide for the full flow.
Comparison: Before and After
Section titled “Comparison: Before and After”| Aspect | Before (serviceId) | After (Service VDS) |
|---|---|---|
| Service identification | Plain string registered by admin | Cryptographically signed credential |
| Auth request | { tokenId, serviceId, scopes } | { tokenId, serviceVds, scopes } |
| Server verification | Database lookup | VDS signature verification |
| Mobile display | Self-declared service name | Verified service name from VDS |
| Key management | serviceId + optional API key | No shared secrets — VDS is self-contained |
| Service compromised | Rotate API key, update all clients | Revoke VDS certificate |
| User trust signal | ”Registered” badge | ”VERIFIED (VDS)” badge |
Advanced: Issuance Models
Section titled “Advanced: Issuance Models”The sections above assume a signed Service VDS exists — but who signs it? Two models are possible:
Model A: Server-Signed (default) — The IDToken Auth Server signs the Service VDS with its own ECDSA P-256 key (the same key used for JWTs). Simple, suitable for single-operator deployments.
Model B: CA-Signed (extended) — A Certificate Authority in the ESEDS trust chain (ISO 22385) signs the Service VDS. This enables cross-operator service identity — a service verified by one CA can authenticate against any IDToken deployment trusting that CA.
| Aspect | Model A (Server-Signed) | Model B (CA-Signed) |
|---|---|---|
| Trust anchor | IDToken server JWKS | ESEDS PKI chain |
| Registration | Admin registers via Console | CA verifies organization identity |
| Revocation | Remove from registry | CA publishes VRL |
| Cross-operator | Single deployment only | Any deployment trusting the CA |
Most deployments start with Model A. Model B is relevant for open ecosystems where services are not pre-registered with a single IDToken operator. See the Trust Architecture page for details on the PKI chain.