Attestation-based client authentication
LumoAuth supports attestation-based client authentication, advertised as attest_jwt_client_auth in the discovery document. Instead of a shared client secret, a client instance proves who it is with two HTTP headers: a statement from a party you trust (the attester) vouching for that instance and its key, and a fresh proof that the instance holds the key. Use it when every installed copy of an app, or every spawned agent, needs its own credential. Terms are explained in the glossary.
| Header | Contents |
|---|---|
OAuth-Client-Attestation | Client Attestation JWT — issued by your attester (device attestation service or app backend), vouching for the client instance and its key |
OAuth-Client-Attestation-PoP | Client Attestation PoP JWT — freshly signed by the client instance's own key, proving possession |
This is designed for app instances that cannot hold a shared secret (mobile wallets, dynamically spawned agents): each instance has its own key pair, and your attester vouches for it. Authentication strength is equivalent to private_key_jwt.
Enabling it for a client
Attestation-based auth is a per-client opt-in — with it disabled the header pair is never accepted. In the Organization Portal open your application's Security tab and configure, in the FAPI 2.0 and client authentication section below the client authentication method:
| Setting | Meaning |
|---|---|
Attestation-based client auth (attest_jwt_client_auth) | The opt-in switch |
| Attester issuer | Expected iss of the Client Attestation JWT |
| Attester JWKS URI | HTTPS URL of the attester's public signing keys (fetched with SSRF guards, cached 15 minutes) |
Both attester fields are required; if either is missing the method is refused. Click Save FAPI settings to apply.

What LumoAuth validates
Client Attestation JWT (OAuth-Client-Attestation):
- JOSE header:
typ=oauth-client-attestation+jwt;algfrom the asymmetric allow-list (RS256/384/512,PS256/384/512,ES256/384/512) — HMAC andnoneare rejected before any key work - Signature verifies against the registered Attester JWKS URI
iss= the registered Attester Issuersub= theclient_idof the client being authenticatedexppresent and in the future (60 s clock skew allowed)cnf.jwkpresent — the client instance's public key
Client Attestation PoP JWT (OAuth-Client-Attestation-PoP):
- JOSE header:
typ=oauth-client-attestation-pop+jwt; same asymmetricalgallow-list - Signature verifies against the
cnf.jwkkey from the attestation JWT iss= theclient_idaud= the authorization server's issuer identifier (theissuervalue from/.well-known/openid-configuration; custom-domain issuers are honoured)exppresent, at most 10 minutes in the futurejtipresent and single-use — replays within the PoP's validity window are rejected
Any failure yields the standard invalid_client error. When the header pair is present, LumoAuth never falls back to weaker authentication methods for that request.
Example token request
POST /orgs/acme-corp/api/v1/oauth/token HTTP/1.1
Host: app.lumoauth.dev
Content-Type: application/x-www-form-urlencoded
OAuth-Client-Attestation: eyJ0eXAiOiJvYXV0aC1jbGllbnQtYXR0ZXN0YXRpb24rand0Iiwi...
OAuth-Client-Attestation-PoP: eyJ0eXAiOiJvYXV0aC1jbGllbnQtYXR0ZXN0YXRpb24tcG9wK2p3dCIsIm...
grant_type=client_credentials&client_id=client-attest-1&scope=reports.send
PoP JWT claims minted by the client instance for this request:
{
"iss": "client-attest-1",
"aud": "https://app.lumoauth.dev/orgs/acme-corp/api/v1",
"exp": 1767225600,
"jti": "5f3c1c9e-1b2a-4c1e-9d3f-8a7b6c5d4e3f"
}
See also
- Token endpoint — where client authentication happens
- FAPI 2.0 —
private_key_jwtand MTLS alternatives - Agent attestation — workload-identity attestation for AI agents (a related but separate mechanism)
Standards used on this page
| Plain name | Standard |
|---|---|
| Attestation-based client authentication | OAuth 2.0 Attestation-Based Client Authentication (IETF draft) |
| Discovery document | RFC 8414 and OpenID Connect Discovery |
| JWT client authentication and the JWT bearer grant | RFC 7523 |
See the standards reference for what each one covers and how LumoAuth uses it.