JWT Bearer
Prove who you are with a signed JWT instead of a shared secret
- Who's involved
- Your service, auth server
- Best for
- Authenticating without a shared secret
- Difficulty
- Advanced
1Overview
How it works
RFC 7523 lets a client use a signed JWT (a small, signed token) in place of a client_secret. This page shows the most common use, called private key JWT client authentication:
- The client keeps a private key and registers the matching public key.
- For each token request it signs a short-lived JWT and sends it as
client_assertion. - The login server verifies the signature and issues the token.
The RFC also defines a separate grant, urn:ietf:params:oauth:grant-type:jwt-bearer, where a JWT sent as assertion is itself the authorization to get a token.
2Use cases
When would you use this?
A service signing its own proof
A backend signs a JWT with its private key to get a token, with no shared secret to leak.
Google service accounts
A job reads files from a cloud service using a key file, as Google service accounts do.
Acting for a user, in advance
An admin-approved integration calls an API for users without a login screen each time.
Not the right fit? Want something simpler? Client Credentials with a secret is easier when a shared secret is acceptable.
3Interactive
Try it step by step
See it step by step
Step 1 of 6
The service shares its public key
Once, at set-up, it gives the login server a public key. The matching private key never leaves the service.
4Practical advice
Do this, avoid that
Good practices
- Keep the JWT short-lived โ A minute or less is plenty
- Give each JWT a unique
jtiโ The server can then reject replays - Set
audto the token endpoint โ A JWT for one server should not work on another - Publish keys with a
kidโ It makes rotation smooth
Common mistakes
- Sharing or logging the private key โ Anyone with it can act as your client
- Long-lived assertions โ A captured one stays useful for too long
- Skipping
issandsubโ Both must equal yourclient_id - Using weak algorithms โ Prefer
RS256,PS256orES256, nevernone
5Code
Code example
Show the code
// Private key JWT client authentication (RFC 7523)
import { SignJWT } from 'jose';
const assertion = await new SignJWT({})
.setProtectedHeader({ alg: 'RS256', kid: KEY_ID })
.setIssuer(CLIENT_ID)
.setSubject(CLIENT_ID)
.setAudience('https://auth.provider.com/oauth/token')
.setJti(crypto.randomUUID())
.setExpirationTime('60s')
.sign(privateKey);
const res = await fetch('https://auth.provider.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: assertion,
scope: 'api.read',
}),
});
const { access_token } = await res.json();