Client Attestation
Client Attestation is offered as a closed beta preview feature and is not generally available yet. If you are interested in trying this feature, please contact us.
Overview
Client Attestation enables credential issuers to restrict credential issuance to trusted wallet applications only. This capability provides strong authentication of wallet instances by allowing wallets to cryptographically prove their authenticity before claiming credentials.
This mechanism is particularly valuable when:
- Enforcing wallet policies: Ensuring that only authorized and trusted wallet applications can receive credentials from your issuer.
- Protecting against unauthorized access: Preventing credential theft by verifying the wallet's identity before issuance.
- Supporting platform-based attestation: Leveraging platform-specific security mechanisms (such as mobile app attestation) while presenting them in a standardized format to the issuer.
Client Attestation provides authentication for wallet applications. The holder (wallet) application developer must implement the necessary cryptographic operations to provide the required attestation proofs. For implementation guidance, please contact us.
Standards and specifications
Client Attestation in MATTR VII is based on the following standards:
- OAuth 2.0 Attestation-Based Client Authentication - The core specification for client attestation
- RFC 9449: DPoP (Demonstrating Proof-of-Possession) - DPoP specification
- DPoP Optimization (Combined Mode) - Based on proposed improvements to the attestation specification
How it works
Client Attestation operates through a certificate-based trust chain between three parties:
- Client Attester (Wallet Backend): Manages certificates and issues attestation JWTs to trusted wallet instances.
- Client Instance (Wallet Application): The individual wallet app on a user's device that requests and claims credentials, holds a key pair, and shares its public key so that MATTR VII can validate proof of possession.
- MATTR VII (Credential Issuer): Validates the attestation and issues credentials only to trusted wallet instances. If validation fails, MATTR VII does not issue a credential and returns an error to the wallet.
The following diagram illustrates how the three participants interact across the different attestation modes:
The client attestation flow begins when a wallet instance attests its own identity to its backend—typically by presenting key information or a unique instance identifier. If the backend is satisfied with this attestation, it issues an attestation JWT that cryptographically verifies the wallet's authenticity. The wallet then presents this attestation, along with proof that it controls the associated cryptographic keys, to MATTR VII. MATTR VII validates the entire chain of trust—from the attestation certificate back to a configured root certificate—before allowing credential issuance to proceed.
Understanding the flow
The attestation process involves three stages:
Attestation issuance
The wallet backend creates an Attestation JWT that includes a certificate chain in its header. This chain must trace back to one of the root certificates that MATTR VII has been configured to trust for this wallet application. The wallet backend signs this JWT with the private key corresponding to the first certificate in the chain and returns it to the wallet instance.
Token request with proof of possession
The wallet instance must prove it controls the private key associated with the public key embedded in the Attestation JWT. How this proof is provided depends on which mode is used:
- Standard Mode: The wallet generates a separate Attestation PoP (Proof of Possession) signed with its instance key and sends both the Attestation JWT and PoP to MATTR VII. This returns a standard Bearer access token.
- Combined Mode (recommended): When using DPoP for token binding, the wallet generates a single DPoP proof that serves as both the attestation proof and the token binding. This reduces complexity by eliminating the need for a separate Attestation PoP. MATTR VII returns a DPoP-bound access token (and optionally a refresh token).
- Standard Mode with DPoP: The wallet provides both an Attestation PoP and a DPoP proof, resulting in a DPoP-bound access token with additional PoP binding for refresh tokens.
MATTR VII validates the entire attestation—verifying the certificate chain, checking signatures, and confirming proof of possession—before issuing the access token.
Credential issuance
The wallet uses the validated access token to request credentials from MATTR VII's credential endpoint. Only after successful attestation validation does MATTR VII issue the credential.
Certificate trust chain
The trust model relies on certificates:
- Root Certificates: The wallet backend establishes root certificates that MATTR VII must trust. Currently these root certificates are shared with MATTR through an out-of-band configuration process.
- Certificate Chain: When the wallet backend creates an Attestation JWT, it includes a certificate chain of end-entity (and possibly intermediate certificates) in the
x5cheader. This chain must verify back to one of the configured root certificates. - Signature Verification: MATTR VII validates that the Attestation JWT is signed by the private key corresponding to the first certificate included in the
x5ccertificate chain, and that the chain verifies to a configured root certificate.
Setting up client attestation
To enable client attestation for your issuer tenant, you need to:
-
Configure trusted wallet applications: Configure which wallet applications are trusted by your tenant. This includes configuring:
- The wallet's client ID
- Root certificates that the wallet backend uses
- Whether DPoP is required for this wallet
-
Share root certificates: Provide the root certificates from your wallet backend's certificate infrastructure to MATTR.
-
Configure attestation requirements: Determine whether client attestation is required or optional for each wallet application.
Currently, client attestation configuration is managed through out-of-band processes. APIs for wallet trust configuration are planned for future releases.
Attestation proofs
When requesting tokens from MATTR VII, the wallet must include attestation proofs in the request headers. The proofs consist of:
- Attestation JWT: A JWT issued by the wallet backend that attests to the wallet instance's identity and key possession.
- Attestation DPoP: A proof that the wallet instance possesses the private key corresponding to the public key in the Attestation JWT.
Three modes of client attestation are available, based on what the wallet provides:
| Mode | What the wallet sends | When to use |
|---|---|---|
| Standard Mode | Attestation JWT + Attestation PoP | When the wallet does not use DPoP |
| Combined Mode | Attestation JWT + DPoP | When the wallet uses DPoP for token binding. The DPoP proof serves as both the attestation proof and the token binding proof. |
| Standard Mode with DPoP | Attestation JWT + Attestation PoP + DPoP | When the wallet uses DPoP for token binding but provides separate attestation and DPoP proofs. |
Combined Mode is the recommended approach when using DPoP, as it reduces the number of proofs the wallet must generate and include in requests.
Attestation JWT
The Attestation JWT is issued by the wallet backend (Client Attester) and attests that a specific wallet instance possesses a particular cryptographic key.
Structure
{
"alg": "ES256",
"typ": "oauth-client-attestation+jwt",
"x5c": [
"MIIC3zCCAcWgAwIBAgIUJ9R0z5r3e7...base64-cert-leaf...",
"MIIDdzCCAl+gAwIBAgIUFp93k2m9a8...base64-intermediate..."
]
}x5c: Certificate chain that must verify back to a configured root certificate for the requestedclient_id.
{
"sub": "example-wallet-client-id",
"iat": 1300814780,
"exp": 1300819380,
"client_instance_id": "unique-instance-identifier",
"cnf": {
"jwk": {
"kty": "EC",
"use": "sig",
"crv": "P-256",
"x": "18wHLeIgW9wVN6VD1Txgpqy2LszYkMf6J8njVAibvhM",
"y": "-V4dS4UaLMgP_4fY4j8ir7cl1TXlFdAgcx55o7TkcSA"
}
}
}sub: The wallet client IDiat,exp: Standard JWT time claims for issued at and expirationclient_instance_id: Optional unique identifier for a specific wallet instancecnf.jwk: The public key of the Client Instance Key Pair. Used to verify the signature of DPoP proofs.
How MATTR VII validates it
MATTR VII performs the following validations on the Attestation JWT:
- Signature verification: The JWT must be signed by the key from the first certificate in the
x5carray - Certificate chain validation: The certificate chain in
x5cmust verify up to one of the root certificates configured for this wallet - Time validation: Standard JWT time claim validation (
iat,exp) - Client matching: The
subclaim must match the client ID making the request
Attestation PoP
The Attestation PoP (Proof of Possession) proves that the wallet instance actually possesses the private key corresponding to the public key in the Attestation JWT's cnf claim.
Structure
{
"alg": "ES256",
"typ": "oauth-client-attestation-pop+jwt"
}{
"aud": "https://your-tenant.vii.mattr.global",
"iat": 1300815780,
"jti": "d25d00ab-552b-46fc-ae19-98f440f25064"
}aud: The MATTR VII tenant URLjti: Unique identifier for this proof (prevents replay attacks)iat: Time issued
How MATTR VII validates it
- Signature verification: The PoP must be signed with the private key corresponding to the public key in the Attestation JWT's
cnf.jwk - Time validation: Standard JWT time claim validation
- Replay detection: The
jtiis checked to ensure this PoP has not been used before
Attestation PoP proofs cannot be reused. Each request must include a fresh Attestation PoP with a unique jti.
Demonstrating Proof-of-Possession (DPoP)
When a wallet uses DPoP (Demonstrating Proof-of-Possession) for token binding, it can leverage Combined Mode to simplify attestation. In this mode:
- The wallet generates its DPoP proof using the same key pair referenced in the Attestation JWT's
cnf.jwk - The DPoP proof serves dual purposes:
- Proving possession of the attestation key
- Binding tokens to the wallet
- No separate Attestation PoP is needed, reducing complexity
Structure
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": {
"kty": "EC",
"x": "YcZGQuz_FdAQnLc9065EbC3oOCwYwIVgHRPQR_DwiuM",
"y": "ujp88e0cXJ4JFcydBdKzrQZk35_jn9ecjxZM7dlZeyM",
"crv": "P-256"
}
}{
"htu": "https://your-tenant.vii.mattr.global/v1/oauth/token",
"htm": "POST",
"jti": "4441dede-e4a8-420a-95ee-2f6716bb384d",
"iat": 1764210264,
"ath": "uU0nuZNNPgilLlLX2n2r-sSE7-N6U4DukIj3rOLvzek",
"htcd": "sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:"
}htu: The HTTP URL of the token endpoint being calledhtm: The HTTP method (e.g.,POST)jti: Unique identifier for this proof (prevents replay attacks)iat: Time issuedath: Optional base64url-encoded SHA-256 hash of theaccess_token. Required when using the access token with resource servers; optional for initial token requests.htcd: Optional base64-encoded SHA-256 hash (content digest) of the HTTP request payload used to validate integrity.
How MATTR VII validates it in Combined Mode
When using Combined Mode, MATTR VII:
- Validates the Attestation JWT as described above
- Validates that the public key in the DPoP proof's
jwkheader matches the public key in the Attestation JWT'scnf.jwk - Validates the DPoP proof signature
- Performs standard DPoP validations (URL, method, time, replay detection and optionally request body integrity)
Making token requests with client attestation
Wallets configured to require client attestation must include the attestation proofs as HTTP headers when calling the Token endpoint.
Request headers
Depending on the authentication mode:
POST /v1/oauth/token HTTP/1.1
Host: your-tenant.vii.mattr.global
Content-Type: application/x-www-form-urlencoded
OAuth-Client-Attestation: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QiLCJ4NWMiOlsuLi5dfQ...
OAuth-Client-Attestation-PoP: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbi1wb3Arand0In0...
grant_type=authorization_code&code=...POST /v1/oauth/token HTTP/1.1
Host: your-tenant.vii.mattr.global
Content-Type: application/x-www-form-urlencoded
OAuth-Client-Attestation: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QiLCJ4NWMiOlsuLi5dfQ...
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Ii4uLiIsInkiOiIuLi4ifX0...
grant_type=authorization_code&code=...Response
If the attestation is valid, MATTR VII returns an access token:
{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"token_type": "DPoP",
"expires_in": 86400
}If attestation is invalid or missing when required, an error is returned:
{
"error": "invalid_client",
"error_description": "Client attestation validation failed"
}Client instance identification
The client_instance_id claim in the Attestation JWT enables wallet applications to include a unique identifier for the specific wallet instance requesting tokens. This identifier is then included in the issued JWT access token.
Why use client instance identification
By including a unique client_instance_id in the Attestation JWT, the wallet can:
- Enable instance tracking: Maintain a correlation between specific wallet instances and their issued credentials
- Enable instance-specific credential data: MATTR VII can use the
client_instance_idwhen querying configured claims sources, allowing issuers to retrieve instance-specific data from existing data sources for more refined and personalized issuance journeys
This mechanism is particularly valuable in environments where:
- Multiple wallet instances operate simultaneously (e.g., on different devices).
- Additional verification of the token issuance flow is required.
- Issuers need to customize credential content based on the specific device or application instance receiving the credential.
How it works
The client_instance_id flow operates as follows:
- Unique identifier generated: A unique identifier for a specific wallet instance is generated (e.g., a UUID or device-specific identifier)
- Include in Attestation JWT: The wallet backend includes this identifier in the
client_instance_idclaim when creating the Attestation JWT - MATTR VII extracts and uses the identifier: When processing the token request, MATTR VII:
- Extracts the
client_instance_idfrom the Attestation JWT - Includes it in the issued JWT access token
- Can use it when querying configured claims sources to retrieve instance-specific credential data
- Extracts the
By making the client_instance_id available during credential issuance, MATTR VII enables issuers to tailor credential content based on the specific wallet instance. For example, claims sources can return different data sets, permissions, or attributes based on which device or application instance is receiving the credential, supporting use cases like device-specific entitlements or instance-aware access control.
The following diagram shows how the instance identifier flows through the attestation and token issuance process:
How the identifier flows:
- Generation: The wallet instance creates or retrieves a unique identifier for itself (typically a UUID or device-specific identifier).
- Embedding in attestation: When requesting an Attestation JWT from the wallet backend, the instance provides this identifier. The wallet backend embeds it in the
client_instance_idclaim of the Attestation JWT. - Extraction and propagation: When the wallet requests an access token from MATTR VII, MATTR extracts the
client_instance_idfrom the Attestation JWT and includes it in the issued JWT access token. - Use during issuance: When the wallet subsequently requests credentials, MATTR VII can use the
client_instance_idfrom the access token to query claims sources with instance-specific parameters, enabling personalized credential data based on the specific device or application instance.
Technical details
The client_instance_id claim is optional in the Attestation JWT. When present:
- The Attestation JWT payload includes the following claims:
sub: The subject of the Attestation JWT (for example, the wallet client application).exp: Expiration time of the JWT.client_instance_id: A unique identifier for the specific wallet instance (for example, a UUID).cnf.jwk: The public key material that the wallet proves possession of when requesting tokens.
{
"sub": "example-wallet-client-id",
"client_instance_id": "550e8400-e29b-41d4-a716-446655440000",
"exp": 1300819380,
"iat": 1300814780,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "base64url-encoded-x-coordinate",
"y": "base64url-encoded-y-coordinate",
"kid": "wallet-key-id"
}
}
}-
And the JWT access token will contain the same value:
JWT Access Token payload { "sub": "user-subject-identifier", "client_id": "example-wallet-client-id", "client_instance_id": "550e8400-e29b-41d4-a716-446655440000", "exp": 1300819380, "iat": 1300814780 } -
JWT access tokens only: The
client_instance_idis included only in JWT-format access tokens. JWT access tokens are issued when credential refresh (refresh tokens) is enabled for the tenant; if your tenant uses opaque access tokens (no credential refresh), this claim will not be present.
Requirements and scope
Client instance identification is available under the following conditions:
- This feature applies only to the Authorization Code flow and is not supported for the Pre-authorized Code flow.
- JWT access tokens only: The
client_instance_idis included only in JWT-format access tokens (when credential refresh is enabled for the tenant), not in opaque tokens. - Client attestation enabled: The wallet must be configured for client attestation with the appropriate feature flags and configuration.
Using client instance ID with claims sources
When the client_instance_id is provided through client attestation, MATTR VII makes it available for use in Claims source requests during credential issuance. This enables issuers to retrieve instance-specific data from external systems based on which specific wallet instance is claiming the credential and supports several use cases:
- Device-specific entitlements: Return different credential claims or permissions based on which device is receiving the credential
- Instance-aware data filtering: Filter or customize credential data based on the specific wallet instance
- Audit and tracking: Track which wallet instances have claimed credentials for compliance or security monitoring
- Multi-device management: Manage credentials across multiple devices belonging to the same user, with instance-specific attributes
During the credential endpoint request, MATTR VII queries configured claims sources to retrieve additional credential data. When client attestation is in use and a client_instance_id is present in the access token, MATTR VII includes this information in the data available for request parameter mapping:
{
"authenticationProvider": {
"url": "https://auth-provider.example.com",
"subject": "user-123"
},
"claims": {
// ... user persisted claims
},
"credentialConfiguration": {
"id": "credential-config-id",
"type": "VerifiableCredential",
"profile": "mso_mdoc"
},
"client": {
"instanceId": "550e8400-e29b-41d4-a716-446655440000"
}
}When creating or configuring a claims source, you can map the client.instanceId to request parameters that will be sent to your claims source server:
{
"url": "https://claims.example.com/api/credentials",
"requestParameters": {
"accountType": {
"mapFrom": "claims.accountType"
},
"sub": {
"mapFrom": "authenticationProvider.subject"
},
"deviceId": {
"mapFrom": "client.instanceId"
}
}
}With this configuration, when MATTR VII queries the claims source during credential issuance, it will send a request like:
GET https://claims.example.com/api/credentials?accountType=premium&sub=user-123&deviceId=550e8400-e29b-41d4-a716-446655440000The client.instanceId will only be available when client attestation is enabled and the wallet backend includes the client_instance_id in the Attestation JWT. If the client.instanceId is not available (e.g., when client attestation is not in use), the mapped parameter will be omitted from the claims source request. Design your claims source endpoints to handle requests both with and without the instance ID parameter, unless you exclusively use client attestation for all issuance flows.
Refresh token binding
When issuing refresh tokens (in conjunction with Credential Refresh), MATTR VII binds the refresh token to the client attestation:
- Combined Mode: Refresh token binding is handled through DPoP binding (as DPoP serves as the attestation proof)
- Standard Mode: Refresh token is bound to the public key in the Attestation JWT's
cnf.jwk
When the wallet uses the refresh token to obtain a new access token:
- The wallet must provide the same attestation proofs (Attestation JWT + DPoP)
- The public key in the new Attestation JWT must match the key from the original token request
- If the keys don't match, the refresh request will be rejected
This ensures that only the original wallet instance can use a refresh token, even if the refresh token itself is compromised.
Implementation considerations
Certificate management
- Root certificates should be protected and rotated according to your organization's security policies
- Multiple root certificates can be configured to support certificate rotation without service interruption
- Expired or compromised certificates should be removed from the trusted configuration immediately
Key management
- Client Instance Key Pairs should be generated and stored securely on the wallet device
- Private keys should never leave the device or be transmitted to any server
- Consider using hardware-backed keystores or secure enclaves when available
Replay protection
- Always include a unique
jtiin Attestation PoP and DPoP proofs - MATTR VII maintains replay detection to prevent reuse of proofs
- Attestation JWTs can be reused within their validity period, but Attestation PoP proofs cannot
Time synchronization
- Ensure wallet devices have accurate time synchronization
- Clock skew can cause valid attestations to be rejected
- Use reasonable validity windows in
iatandexpclaims to account for minor time differences
Client Instance Validation
When implementing client instance identification:
- Unique identifiers: Use truly unique identifiers for each wallet instance to prevent collisions
- Identifier format: While any string value is supported, UUIDs or similar globally unique identifiers are recommended
- Security implications: The
client_instance_idis not encrypted in the JWT access token, so avoid including sensitive information in the identifier value
How would you rate this page?
Last updated on