Skip to main content

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.

HeaderContents
OAuth-Client-AttestationClient Attestation JWT — issued by your attester (device attestation service or app backend), vouching for the client instance and its key
OAuth-Client-Attestation-PoPClient 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:

SettingMeaning
Attestation-based client auth (attest_jwt_client_auth)The opt-in switch
Attester issuerExpected iss of the Client Attestation JWT
Attester JWKS URIHTTPS 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.

The FAPI 2.0 and client authentication section with the attestation-based client auth switch and attester fields

What LumoAuth validates​

Client Attestation JWT (OAuth-Client-Attestation):

  • JOSE header: typ = oauth-client-attestation+jwt; alg from the asymmetric allow-list (RS256/384/512, PS256/384/512, ES256/384/512) — HMAC and none are rejected before any key work
  • Signature verifies against the registered Attester JWKS URI
  • iss = the registered Attester Issuer
  • sub = the client_id of the client being authenticated
  • exp present and in the future (60 s clock skew allowed)
  • cnf.jwk present — the client instance's public key

Client Attestation PoP JWT (OAuth-Client-Attestation-PoP):

  • JOSE header: typ = oauth-client-attestation-pop+jwt; same asymmetric alg allow-list
  • Signature verifies against the cnf.jwk key from the attestation JWT
  • iss = the client_id
  • aud = the authorization server's issuer identifier (the issuer value from /.well-known/openid-configuration; custom-domain issuers are honoured)
  • exp present, at most 10 minutes in the future
  • jti present 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_jwt and MTLS alternatives
  • Agent attestation — workload-identity attestation for AI agents (a related but separate mechanism)

Standards used on this page​

Plain nameStandard
Attestation-based client authenticationOAuth 2.0 Attestation-Based Client Authentication (IETF draft)
Discovery documentRFC 8414 and OpenID Connect Discovery
JWT client authentication and the JWT bearer grantRFC 7523

See the standards reference for what each one covers and how LumoAuth uses it.