Private Key JWT Authentication
Private Key JWT Authentication
Private Key JWT—usually identified by the OAuth client-authentication method name private_key_jwt—allows an application to authenticate using asymmetric cryptography instead of sending a shared client secret.
The client creates a short-lived JSON Web Token, signs it with its private key and submits the signed JWT to the authorization server. The authorization server validates the signature using the client’s registered public key.
If validation succeeds, the authorization server authenticates the client and processes the accompanying OAuth grant request.
This method is commonly used with:
- OAuth 2.0
- OpenID Connect
- Machine-to-machine applications
- Server-to-server integrations
- Confidential web applications
- Financial, healthcare and government APIs
- Systems that require stronger client authentication than a shared secret provides
The Essential Concept
With traditional client-secret authentication, the client and authorization server share the same secret:
Client Secret → Known by Client and Authorization Server
With Private Key JWT authentication, the keys are different:
Private Key → Held only by the Client
Public Key → Registered with the Authorization Server
The private key is used to create a digital signature. The public key verifies that signature but cannot be used to produce a valid one.
The private key should therefore never be transmitted to the authorization server or included in an HTTP request.
What Does Private Key JWT Actually Authenticate?
Private Key JWT normally authenticates an OAuth client to the authorization server’s token endpoint.
It does not normally function as the access token sent to the business API.
The sequence is:
Client creates signed assertion
↓
Client sends assertion to Authorization Server
↓
Authorization Server authenticates Client
↓
Authorization Server issues Access Token
↓
Client sends Access Token to API
This distinction is important:
- The client assertion proves the identity of the client.
- The access token authorizes access to the protected API.
A Private Key JWT assertion is therefore a replacement for a client secret—not a replacement for the OAuth access token.
Private Key JWT and the OAuth Grant Type
Client authentication and the OAuth grant type are separate decisions.
Private Key JWT can authenticate a confidential client while that client uses several different OAuth grants.
Client Credentials Grant
For machine-to-machine communication:
grant_type=client_credentials
client authentication=private_key_jwt
The resulting access token represents the application or workload.
Authorization Code Grant
For an application acting with a user:
grant_type=authorization_code
client authentication=private_key_jwt
The user authenticates through the front-channel flow. The confidential application later uses its signed client assertion when exchanging the authorization code for tokens.
The same client-authentication method can therefore support both user-delegated and machine-to-machine scenarios.
How Private Key JWT Authentication Works
Step 1: Generate a cryptographic key pair
The client creates an asymmetric key pair:
- A private signing key
- A corresponding public verification key
Common signing algorithms include:
- RSA with SHA-256, identified as
RS256 - RSA-PSS with SHA-256, identified as
PS256 - ECDSA using P-256 and SHA-256, identified as
ES256
The authorization server determines which algorithms it accepts.
Step 2: Protect the private key
The private key remains under the client’s control.
It may be stored in:
- A Hardware Security Module
- A cloud Key Management Service
- A managed secret or key vault
- A protected operating-system certificate store
- A hardware-backed keystore
- An encrypted application keystore
The application should ideally request a signing operation from the key-management system rather than retrieving and handling the raw private-key material.
Step 3: Register the public key
The authorization server must know which public key belongs to the client.
The public key may be registered as:
- A JSON Web Key
- A JSON Web Key Set
- A
jwks_urithat publishes the client’s public keys - An X.509 certificate containing the public key
- A key entered directly into the client-registration system
The public key does not necessarily need to come from a public Certificate Authority. What matters is that the authorization server has a trusted association between the public key and the registered OAuth client.
Step 4: Create the JWT header
The client creates a JWT header such as:
{
"alg": "PS256",
"typ": "JWT",
"kid": "client-key-2026-01"
}
The fields serve the following purposes:
algidentifies the signing algorithm.typidentifies the object as a JWT.kididentifies the public key that should be used to verify the signature.
The kid becomes especially useful during key rotation, when more than one public key may be valid.
Step 5: Create the JWT claims
A typical Private Key JWT payload might look like this:
{
"iss": "billing-service",
"sub": "billing-service",
"aud": "https://identity.example.com/oauth2/token",
"iat": 1789732800,
"exp": 1789733100,
"jti": "9a751f9b-08d4-45b2-813b-30a92b3a94e7"
}
The important claims are:
iss — Issuer
The issuer identifies the client that created the assertion.
For Private Key JWT authentication, this is normally the OAuth client_id.
"iss": "billing-service"
sub — Subject
The subject identifies the client being authenticated.
For client authentication, the subject must be the OAuth client_id.
"sub": "billing-service"
For the usual Private Key JWT case:
iss = client_id
sub = client_id
aud — Audience
The audience identifies the authorization server for which the assertion was created.
Depending on the provider’s requirements, it may be:
- The token endpoint URL
- The authorization server’s issuer identifier
- Another specifically documented authorization-server identifier
The client must use the exact audience value expected by the authorization server.
"aud": "https://identity.example.com/oauth2/token"
iat — Issued At
This identifies when the assertion was created.
"iat": 1789732800
It is expressed as a NumericDate—the number of seconds since the Unix epoch.
exp — Expiration Time
This defines when the assertion expires.
"exp": 1789733100
Client assertions should be short-lived. A lifetime of a few minutes is common, although the authorization server’s documented policy controls the permitted maximum.
jti — JWT ID
The JWT ID uniquely identifies the assertion.
"jti": "9a751f9b-08d4-45b2-813b-30a92b3a94e7"
The authorization server can temporarily store previously used jti values and reject duplicate submissions. This provides replay protection during the assertion’s validity period.
The core JWT profile and processing requirements are defined in RFC 7523.
Step 6: Sign the JWT
The client signs the encoded header and payload using its private key.
The resulting JWT has three parts:
BASE64URL(header).BASE64URL(payload).BASE64URL(signature)
For example:
eyJhbGciOiJQUzI1NiIsImtpZCI6ImNsaWVudC1rZXktMjAyNi0wMSJ9
.
eyJpc3MiOiJiaWxsaW5nLXNlcnZpY2UiLCJzdWIiOiJiaWxsaW5nLXNlcnZpY2Uif