Most OAuth clients still prove who they are with a client secret, a shared password that is copied into config files, pipelines and vaults and works for anyone who finds it. JWT client authentication, registered in OAuth as private_key_jwt, replaces that secret with a short-lived assertion signed by a private key that never leaves the client. It is defined by RFC 7523, recommended by the OAuth Security Best Current Practice (RFC 9700) and required, alongside mutual TLS, by the FAPI 2.0 Security Profile. This guide explains how the mechanism works, what the authorisation server must validate, the audience change introduced by the RFC 7523 update, how to manage the client's keys and how to migrate a fleet of confidential clients off secrets. It is written for identity engineers, platform teams and anyone who owns machine-to-machine integrations.
Key takeaways
- A signed assertion is not a password. The authorisation server holds only the client's public key, so a database leak or a copied log line no longer hands out a reusable credential.
- Set the audience to the issuer identifier. The RFC 7523 update (draft-ietf-oauth-rfc7523bis) makes the issuer the only permitted
audfor client authentication, closing the audience injection class of attacks. - Keep assertions short and single use. Expiry of a few minutes, a unique
jtiand server-side replay tracking turn a stolen assertion into a near-worthless artefact. - The private key is now the crown jewel. Generate it in a KMS or HSM where you can, publish the public half through
jwks_uriand rotate by overlapping keys rather than swapping them. - Migrate in rings and then switch secrets off. A client that can still fall back to
client_secret_basichas not been hardened; the last step is removing the secret and refusing the old method.
Why client secrets are the weak point
A client secret is a symmetric credential. Both sides know it, it is presented on every token request, and anything that can read it can impersonate the client. In practice it ends up in environment variables, CI variables, helm values, wiki pages and support tickets, and RFC 7591 even allows client_secret_expires_at to be 0, meaning the secret never expires. The NHI Authentication Guide covers the wider choice of machine credentials; this page goes deep on one of the strongest options for OAuth clients.
- Exposure on use: a secret sent in
client_secret_basicorclient_secret_postcrosses every proxy and logging layer on the way to the token endpoint. A signed assertion also crosses them, but it expires in minutes and is bound to one audience. - Exposure at rest on the server: the authorisation server must store something that verifies the secret. With
private_key_jwtit stores a public key, which is not sensitive. - Theft techniques: attackers harvest secrets from files and repositories (MITRE ATT&CK T1552.001 Credentials In Files) and add their own credentials to existing applications (T1098.001 Additional Cloud Credentials). Both are easier when the credential is a copyable string.
- What the standards say: RFC 9700 Section 2.5 states that it is RECOMMENDED to use asymmetric cryptography for client authentication, naming mutual TLS and
private_key_jwtas examples.
Client authentication methods compared
| Method (token_endpoint_auth_method) | Credential the client holds | What the server stores | Main weakness | Typical fit |
|---|---|---|---|---|
client_secret_basic / client_secret_post | Shared secret | Secret or its hash | Replayable string, sent on every request | Legacy integrations, low-risk internal tools |
client_secret_jwt | Shared secret used as an HMAC key | The secret itself (needed to verify the MAC) | Still symmetric: a server-side leak exposes it | Rarely the right choice today |
private_key_jwt | Private key (RSA or EC) | Public key or jwks_uri | Private key theft from the workload; audience mistakes | Server-side apps, M2M, FAPI ecosystems, multi-cloud |
tls_client_auth (RFC 8705) | Private key plus CA-issued certificate | Expected subject DN or SAN | Needs PKI and TLS termination that passes the certificate | Organisations with mature PKI, open banking |
self_signed_tls_client_auth (RFC 8705) | Private key plus self-signed certificate | Certificate in the client's JWKS (x5c) | Same TLS plumbing, no chain validation | mTLS without running a CA |
| Workload identity federation | Platform-issued token (Kubernetes, CI, cloud) | Trust policy for the external issuer | Trust policy too broad (wildcard subjects) | Workloads on platforms that issue identity tokens |
Choosing: pick private_key_jwt when TLS termination makes mutual TLS awkward (load balancers, API gateways, SaaS token endpoints) or when one client talks to several authorisation servers. Pick mutual TLS when you already run a PKI and want the same certificate to sender-constrain tokens. Pick federation when the platform already gives the workload a verifiable identity, which removes even the long-lived private key. See the mTLS Guide and the Machine-to-Machine OAuth (Client Credentials) Guide.
How private_key_jwt works
- Registration: the client registers
token_endpoint_auth_method=private_key_jwtand either ajwks_urior an inlinejwks(RFC 7591 says the two MUST NOT both be present, and prefersjwks_uribecause it allows easier key rotation). - Discovery: the client reads the authorisation server metadata (RFC 8414) at
/.well-known/oauth-authorization-serveror the OpenID equivalent, checks thatprivate_key_jwtappears intoken_endpoint_auth_methods_supported, and picks an algorithm fromtoken_endpoint_auth_signing_alg_values_supported. RFC 8414 requires that list whenprivate_key_jwtis advertised and forbidsnonein it. - Assertion: for each request the client builds a fresh JWT, signs it with its private key and sends it as
client_assertionwithclient_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer. RFC 7523 allows exactly one JWT in that parameter. - Validation: the server finds the client's key (by
kid), verifies the signature, checks the claims and only then processes the grant. A bad assertion returnsinvalid_client(RFC 7523 Section 3.2).
The same assertion format is used wherever the client authenticates to the server: token, pushed authorisation request (PAR), introspection and revocation endpoints. RFC 8414 has matching *_auth_methods_supported metadata for each.
Assertion claims and header
| Field | Value for client authentication | Source and notes |
|---|---|---|
iss | The client_id | RFC 7523 Section 3, required |
sub | The client_id | RFC 7523: for client authentication the subject MUST be the client_id |
aud | The authorisation server's issuer identifier, as a single string | draft-ietf-oauth-rfc7523bis; FAPI 2.0 5.3.3.1 item 05 says a string, not an item in an array |
exp | A few minutes after issue | Required. Microsoft Entra documentation advises 5 to 10 minutes after nbf at most |
iat, nbf | Current time | Optional in RFC 7523; servers may reject values unreasonably far in the past. FAPI 2.0 accepts up to 10 seconds of future clock skew |
jti | Unique random identifier | Optional in RFC 7523 but needed for replay detection; treat it as mandatory in your own profile |
Header alg | PS256, ES256 or EdDSA | FAPI 2.0 Section 5.4.1 list; never none |
Header kid | Identifier of the signing key | Lets the server pick the right key during rotation |
Header typ | client-authentication+jwt | SHOULD in the rfc7523bis draft; servers are advised not to reject untyped JWTs yet |
The audience change in RFC 7523bis
RFC 7523 allowed the audience to be the token endpoint URL and left the exact strings to be agreed out of band. Researchers at the University of Stuttgart (Hosseyni, Küsters and Würtele, IACR ePrint 2025/629, "Audience Injection Attacks") showed that when a client signs assertions whose audience is an endpoint value it learnt from metadata, a malicious authorisation server can supply values that make the client mint an assertion usable at an honest server. The paper lists OAuth 2.0, OpenID Connect, FAPI, CIBA and the device authorisation grant among the affected standards. The issue was disclosed to the OAuth Working Group in January 2025.
- What the update says: draft-ietf-oauth-rfc7523bis (version -11, with the RFC Editor and marked "blocked: Final Approval Pending" when checked on 27 October 2026) makes the issuer identifier the only permitted audience for client authentication JWTs, says the token endpoint URL MUST NOT be used as an audience value, and requires the server to reject any JWT that does not contain its issuer identifier as its sole audience value.
- Knock-on changes: the draft also updates RFC 7521, RFC 7522 and RFC 9126 (PAR) so the same rule applies to assertions sent to the PAR endpoint. Authorisation grants (RFC 7523 Section 2.1) still allow either the issuer or the token endpoint URL.
- FAPI 2.0 already requires it: Section 5.3.2.1 item 08 says the server shall only accept its issuer identifier as a string in the
audclaim. - Vendor reality: some providers still document the token endpoint URL as the audience. Microsoft Entra, for example, documents
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token. Follow the provider's documented value until it changes, and track the change.
Because the draft is not yet an RFC, check its final text before writing it into policy; the core audience rule has been stable across recent versions.
What the authorisation server must validate
- Method lock: accept only the authentication method registered for that client. A client registered for
private_key_jwtmust not be able to fall back to a secret. - Key source: resolve keys only from the client's registered
jwksorjwks_uri. Ignorejku,x5uand embeddedjwkheaders in the assertion (FAPI 2.0 Section 5.4.2 advises againstjkuandx5u; ASVS 5.0 V9.1.3 requires key material from trusted pre-configured sources). - Algorithm allowlist: pin the accepted
algvalues per client or per server and rejectnoneand HMAC algorithms for this method. - Claims:
issandsubequal the client_id;audis exactly the issuer identifier;expis in the future and not unreasonably far away (reject lifetimes over a few minutes);iatandnbfsit inside a small skew window. - Replay: store each
jtiuntil the assertion'sexpand reject repeats. RFC 7523 treats replay protection as optional; high-assurance profiles should not. - Key strength: RSA keys of at least 2048 bits and elliptic curve keys of at least 224 bits (FAPI 2.0 Section 5.4).
- JWKS fetching: fetch the client's
jwks_uriover TLS, cache with a sensible maximum age, refetch on an unknownkidwith rate limiting, and restrict outbound fetches so the URI cannot be pointed at internal addresses.
Managing the client's private key
| Option | How signing works | Strength | Trade-off |
|---|---|---|---|
| Cloud KMS or HSM key (non-exportable) | Workload calls the KMS sign API; key never leaves | Theft needs live access to the workload's KMS permission | Latency and cost per signature; KMS permission becomes the credential to guard |
| Secrets manager holding a PEM key | Workload fetches key at start-up and signs locally | Central rotation and audit of reads | Key exists in process memory and can be copied |
| Key generated on the workload, public half registered | Key created at deploy time, never stored elsewhere | No copy outside the host | Needs automated registration of the new public key |
| Key file in the repository or image | Signs locally | None worth having | Same exposure as a client secret: avoid |
- One key per client, per environment: never share a key across clients or between production and test.
- Prefer
jwks_uri: it lets the client publish the next key before using it, which is the basis of zero-downtime rotation. See the JSON Web Key Sets and Signing Key Rotation Guide for the overlap pattern. - Rotation cadence: rotate on a schedule (for example yearly for KMS-held keys, more often for exported keys) and immediately on suspected exposure. Rotation means publish new key, start signing with it, then remove the old one after caches expire.
- Ownership: record an owner, environment and expiry for every client key in your NHI inventory, as you would for any credential. See the Cryptographic Key Management Guide.
Migrating from client secrets: a phased rollout
- Inventory: list every confidential client, its current
token_endpoint_auth_method, secret age and expiry, owner and the endpoints it calls. Flag secrets with no expiry first. - Confirm server support: check the provider's metadata for
private_key_jwtand its supported algorithms, and confirm how it handlesaud(issuer or token endpoint) andjtireplay. - Build one signing library: wrap assertion creation in a shared library or sidecar that sets claims correctly, uses a fresh
jti, keepsexpshort and calls the KMS. Most failures come from hand-rolled assertion code. - Pilot ring: register a public key alongside the existing secret for a handful of low-risk clients, switch them to assertions, and watch token endpoint errors (
invalid_client) and clock-skew rejections for a week. - Wider rings: move clients by business criticality, highest-risk scopes first once the pattern is proven. Keep the secret valid but unused during each ring as the rollback path.
- Remove the fallback: delete the secret, change the registered method so the server refuses secrets for that client, and alert on any secret-based attempt.
- Rollback: if a ring fails, re-enable the secret method for the affected clients only, fix the signing library, and repeat. Never roll back by re-issuing a new long-lived secret broadly.
Small organisations with a single identity provider can often finish in weeks by switching clients one at a time. Large estates with many providers should standardise the library first and track progress as a percentage of clients without secrets.
Detection and measurement
- Log fields: on every token request record client_id, authentication method,
kid,jti, source IP and result. Correlate with the KMS sign logs for the same client. - Alerts: a secret-based attempt for a client registered for
private_key_jwt; repeatedjtivalues; a new key added to a client's registration outside change control (this maps to T1098.001); assertions signed with akidthat is no longer published; token requests from networks the client never uses. - Registration changes: review every change to client credentials. Unreviewed application credentials were part of the path in the Microsoft Midnight Blizzard breach, where a legacy test OAuth application and newly created OAuth apps gave the attacker mailbox access.
| Metric | Target |
|---|---|
| Confidential clients using asymmetric authentication (private_key_jwt or mTLS) | Rising to 100% of production clients |
| Clients that still hold a valid secret after migration | Zero |
Median assertion lifetime (exp minus iat) | 5 minutes or less |
| Client keys held in non-exportable KMS or HSM storage | Above 80% for production |
| Client keys older than the rotation policy | Zero |
Replayed jti rejections investigated within one working day | 100% |
Framework mapping
| Framework | Reference | How private_key_jwt helps |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-9 Service Identification and Authentication | Cryptographic authentication of the client service to the token service |
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management | Key generation, rotation and protection replace shared secret handling |
| NIST SP 800-53 Rev. 5 | SC-12 Cryptographic Key Establishment and Management | Lifecycle of the client's signing key |
| NIST CSF 2.0 | PR.AA-01, PR.AA-03 | Credentials for services are managed; services are authenticated |
| OWASP ASVS 5.0 | V10.4.10, V10.4.16 | Confidential clients authenticated on back-channel requests using strong, public-key based methods |
| MITRE ATT&CK | T1552.001, T1528, T1098.001 | Removes copyable secrets; key additions become a detectable event |
| FAPI 2.0 Security Profile | 5.3.2.1 items 06 and 08 | Client authentication restricted to mTLS or private_key_jwt with issuer audience |
Common failure patterns
- Secret left as a fallback: the client signs assertions, but the old secret still works, so an attacker simply uses the secret. Removing the method is the step that delivers the security gain.
- Long-lived assertions: code that mints one assertion with a 24-hour
expand reuses it has rebuilt a bearer secret. Mint per request. - Private key stored like a secret: a PEM file in the repository or container image gives no advantage over a client secret. Leaked keys in images are common, as the Docker Hub Secrets Leak 2025 showed for many kinds of credential.
- Trusting header-supplied keys: a server that honours
jkuor an embeddedjwklets the attacker choose the verification key. Resolve keys only from registration. - Audience as an array of endpoints: clients that put several endpoint URLs in
aud, or servers that accept any of them, are exactly the pattern the audience injection research exploited. - No replay store: without
jtitracking, an assertion captured from a debug log can be replayed until it expires.
Practitioner checklist
- Inventory confidential clients and record method, secret age, owner and scopes.
- Confirm each authorisation server advertises
private_key_jwtand an algorithm list withoutnone. - Use one shared signing library that sets
iss,sub, issueraud, shortexpand a freshjti. - Generate production client keys in KMS or HSM storage; one key per client per environment.
- Register keys through
jwks_uriwhere possible and rotate by overlap. - On the server, pin algorithms, ignore
jku,x5uandjwkheaders, and trackjtiuntil expiry. - Migrate in rings, keep the secret as a rollback path per ring, then delete it and lock the method.
- Alert on secret attempts, replayed
jtivalues and unplanned key additions. - Track the rfc7523bis draft and update audience handling when providers change.
Standards and references
- IETF: RFC 7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
- IETF: draft-ietf-oauth-rfc7523bis, update to RFC 7523 audience rules (Internet-Draft)
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- OpenID Foundation: FAPI 2.0 Security Profile
- IETF: RFC 8414 OAuth 2.0 Authorization Server Metadata
- IETF: RFC 8705 OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
- IACR ePrint: Audience Injection Attacks (2025/629)
- Microsoft Learn: Microsoft identity platform certificate credentials
Related NHI Mgmt Group resources: NHI Authentication Guide · Azure Entra Client Secrets Security Guide · JSON Web Key Sets and Signing Key Rotation Guide · FAPI 2.0 Security Profile Implementation Guide · OAuth 2.0 Security Best Current Practice Guide