Skip to content

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:

ATTACK: Stolen serviceIdAttacker’s SiteAuth ServerUser’s Phone1. POST /auth/initiateserviceId: “bar-le-central” (stolen)2. Push: “Bar Le Central”3. User sees:“Bar Le Central”Looks legitimate!4. Approves (face + sign)5. Attacker gets JWT!The serviceId is just a string — any party who knows it can impersonate the service

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.

CURRENTUserVDS (signed)ServiceserviceId (string)User proves identity cryptographicallyService states its name — no proofAsymmetric — service not verifiedWITH SERVICE VDSUserVDS (signed)ServiceService VDS (signed)Both sides prove identity cryptographicallyAttacker cannot forge Service VDSSymmetric — both sides verified

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.

Service WebsiteAuth ServerMobile App1. POST /auth/initiate{ tokenId, serviceVds: “HC1:…” }2. Verify VDSECDSA signatureExtract service IDValidate scopes3. FCM Push (verified service)4. { sessionId, OTP, wsToken }VERIFIEDBar Le Centralbar-le-central.fr3478123478125. User compares OTPs + sees verified service6. Face + SignECDSA signature7. POST /auth/verify8. WS: JWT (approved)Both sides verifiedMutual cryptographic identity

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.

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:

  1. Decodes the Base45-encoded Service VDS
  2. Verifies the ECDSA P-256 signature against the server’s JWKS (or CA certificate)
  3. Checks the VDS has not expired
  4. Extracts the service identity: name, URL, logo, authorized scopes
  5. Validates the requested scopes are within the VDS scope ceiling
  6. 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).

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:

VDS-Verified ServiceBBar Le Centralhttps://bar-le-central.frIDENTITY VERIFIED (VDS)Service identity confirmed by cryptographic signatureLegacy (serviceId only)BBar Le Centralhttps://bar-le-central.frREGISTEREDService name from registry — not cryptographically verified
PropertyMechanism
No shared secretsService proves identity via signed VDS — no API keys to distribute, rotate, or leak
Phishing resistanceFake services cannot forge a valid VDS signature without the signing key
Scope enforcementRequested scopes validated against the cryptographically signed scope ceiling
RevocationRevoke the VDS certificate to instantly block a compromised service
User confidenceMobile app displays verified service identity — users know exactly who is asking
Backward compatibleLegacy serviceId flow continues to work alongside VDS-based identification

To adopt Mutual Service Identity, a relying party:

  1. Obtains a Service VDS — either from the IDToken Console (Model A) or a CA (Model B)
  2. Stores the Service VDS — the Base45-encoded VDS is a static credential (valid until expiry)
  3. Passes it in auth requests — include serviceVds instead of serviceId in POST /auth/initiate
// Frontend: initiate auth with Service VDS
const 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.

AspectBefore (serviceId)After (Service VDS)
Service identificationPlain string registered by adminCryptographically signed credential
Auth request{ tokenId, serviceId, scopes }{ tokenId, serviceVds, scopes }
Server verificationDatabase lookupVDS signature verification
Mobile displaySelf-declared service nameVerified service name from VDS
Key managementserviceId + optional API keyNo shared secrets — VDS is self-contained
Service compromisedRotate API key, update all clientsRevoke VDS certificate
User trust signal”Registered” badge”VERIFIED (VDS)” badge

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.

AspectModel A (Server-Signed)Model B (CA-Signed)
Trust anchorIDToken server JWKSESEDS PKI chain
RegistrationAdmin registers via ConsoleCA verifies organization identity
RevocationRemove from registryCA publishes VRL
Cross-operatorSingle deployment onlyAny 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.