Prefer private key JWT where your authorization server supports it. RFC 9700 recommends asymmetric client authentication, such as mutual TLS or signed JWTs, because the server needs to store only public keys, which makes leakage less damaging. A client secret is simpler but is a shared secret that must be stored on both sides.
How each works
Client secret. The client and server share a password. The client sends it, often with HTTP Basic authentication, when calling the token endpoint. Anyone who obtains the string can act as the client.
Private key JWT. The client registers a public key. To authenticate, it signs a short JWT with its private key and sends it as the client_assertion. The server verifies the signature with the public key. The private key is never transmitted.
What the signed JWT contains
The OpenID Connect Core spec lists the claims for the private_key_jwt method:
| Claim | Requirement |
|---|---|
| iss | Required, the client_id |
| sub | Required, the client_id |
| aud | Required, identifies the authorization server, ideally the token endpoint URL |
| jti | Required, a unique ID so the token can be used only once |
| exp | Required, the expiry |
| iat | Optional |
The client_assertion_type parameter must be urn:ietf:params:oauth:client-assertion-type:jwt-bearer. The spec says the server must verify it is an intended audience, and that these tokens must only be used once unless reuse is negotiated.
Comparison
| Question | Client secret | Private key JWT |
|---|---|---|
| What does the server store? | A secret or its hash | A public key |
| If the server database leaks | Client secrets may be exposed | Public keys only |
| If the client leaks its credential | Attacker can authenticate until rotated | Attacker can authenticate until the key is revoked |
| Setup effort | Low | Higher: key generation, signing code |
| Rotation | Replace the string | Publish a new key, then retire the old |
| Replay protection | None built in | jti and short exp |
The main gain is on the server side. RFC 9700 says that with asymmetric client authentication, authorization servers do not need to store sensitive symmetric keys. That removes one source of leakage.
When to switch
- You operate many confidential clients and want to reduce the blast radius of a database leak at the authorization server.
- You can store a private key in a managed key service or hardware module instead of a config file.
- Your authorization server supports the method. Check its metadata for the supported authentication methods.
- You can handle key rotation. Publishing keys through a JWKS URL makes this easier.
Stay with a client secret if the cost of the change outweighs the risk, but treat the secret as a high value credential: keep it in a secrets manager, rotate it on a schedule, and never log it.
Implementation tips
- Set a short
exp, such as one minute, and a freshjtiper request. - Set
audto the token endpoint URL. - Sign with a modern algorithm and publish the
kid. - Do not reuse the same key for signing client assertions and other purposes.
- Alert when a client authenticates from a new location.
Quick check
Does private key JWT fix a stolen token?
No. It protects client authentication, not tokens already issued. Use short lifetimes and sender constraining for tokens.
Can public clients use it?
It only helps when the private key can be kept safe. A key shipped inside an app can be extracted, so public clients rely on PKCE instead.
Safeguard's SBOM and dependency scanning shows which OAuth and JWT libraries your services use, which matters when you add signing code. Start with the free scan.
Sources
- RFC 9700, Best Current Practice for OAuth 2.0 Security
- OpenID Connect Core 1.0, client authentication
- RFC 7523, JWT Profile for OAuth 2.0 Client Authentication
Last reviewed: 19 October 2026.