Skip to main content

OIDC contract for the Wallet Portal

The rule

wallet.oid4pay.com/login uses OIDC against the AS:

  • response_type=code only (never token or id_token; both deprecated)
  • PKCE S256 REQUIRED
  • nonce REQUIRED in auth request and validated in ID Token
  • ID Token claims: iss=https://as.oid4pay.com, sub=<principal_id>, aud=<wallet_client_id>, exp, iat, auth_time, nonce, c_hash, acr, amr
  • acr values: urn:oid4pay:pwd (password baseline), urn:oid4pay:webauthn:2fa (WebAuthn two-factor)
  • amr values array: ["pwd"], ["pwd","webauthn"]
  • ID Token signed with Ed25519; the ID-Token and AT signing keys are separate
  • Wallet Portal validates iss matches as.oid4pay.com per RFC 9207 §2.4 to defeat mix-up attacks
  • state REQUIRED on auth request per RFC 9700 §4.7

Discovery document

The AS publishes its OIDC metadata at the RFC 8414 path:

GET /.well-known/oauth-authorization-server HTTP/1.1
Host: as.oid4pay.com

200 OK
{
  "issuer": "https://as.oid4pay.com",
  "authorization_endpoint": "https://as.oid4pay.com/oauth/authorize",
  "pushed_authorization_request_endpoint": "https://as.oid4pay.com/oauth/par",
  "require_pushed_authorization_requests": true,
  "token_endpoint": "https://as.oid4pay.com/oauth/token",
  "introspection_endpoint": "https://as.oid4pay.com/oauth/introspect",
  "revocation_endpoint": "https://as.oid4pay.com/oauth/revoke",
  "registration_endpoint": "https://as.oid4pay.com/oauth/register",
  "jwks_uri": "https://as.oid4pay.com/oauth/jwks.json",
  "scopes_supported": ["openid", "email", "profile", "oid4ac:payment", "wallet:read", "wallet:write"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["private_key_jwt"],
  "token_endpoint_auth_signing_alg_values_supported": ["EdDSA"],
  "id_token_signing_alg_values_supported": ["EdDSA"],
  "subject_types_supported": ["public"],
  "dpop_signing_alg_values_supported": ["EdDSA", "ES256"],
  "authorization_response_iss_parameter_supported": true,
  "resource_indicators_supported": true,
  "revocation_endpoint_auth_methods_supported": ["private_key_jwt", "none"]
}

ID Token claim set

{
  "iss": "https://as.oid4pay.com",
  "sub": "principal_id_b64url",
  "aud": "wallet_client_id",
  "exp": 1747260600,
  "iat": 1747260300,
  "auth_time": 1747260280,
  "nonce": "<the nonce the RP sent on the auth request>",
  "c_hash": "<b64url(SHA-256(authorization_code)) first half>",
  "acr": "urn:oid4pay:webauthn:2fa",
  "amr": ["pwd", "webauthn"]
}

RP verifier checklist

  1. iss matches the AS issuer the RP is bound to.
  2. Signature against AS JWKS, alg=EdDSA.
  3. aud equals the RP's client_id.
  4. exp in future.
  5. iat within 60 s of clock.
  6. nonce matches the RP-sent nonce.
  7. c_hash matches the redeemed authorization code.
  8. RFC 9207 iss parameter on the authorization response matches the ID Token iss.

RFC 9207 mix-up defence

The authorization response carries an iss parameter on the redirect back to the RP. The RP MUST match it against the issuer it initiated the flow with. Without this check, an attacker substituting a hostile AS during the redirect leg could exchange a code with the legitimate RP's client_id at the wrong server.