JSON Web Token (JWT) profile for OAuth2
JSON Web Token (JWT) for OAuth 2.0 Client Authentication and Authorization Grants is an extension to OAuth2 framework. It allows a client to send a signed JWT token to an OpenID Connect Provider in exchange for an OAuth 2.0 access token.
Usage scenario
ELECTRO, an electric company that needs to receive automatic monthly payments from a customer's online bank account uses this extension. If the electric company and the online bank have a trusted relationship, ELECTRO can send a signed JWT token with the required claims to the OpenID Connect Provider configured for the online bank to request an OAuth 2.0 access token each month. ELECTRO can then use the access token to cash the monthly payments from the online bank.
Ory JWT profile capabilities
Ory supports both methods for using JWTs as authorization grants and client authentication described in RFC 7523:
- Using JWTs as authorization grants allows exchanging JSON Web Tokens for access tokens using an established trust relationship.
- Using JWTs for client authenticationa allows OAuth 2.0 client authentication using public/private keys via JSON Web Tokens.
Using JWTs as Authorization Grants
To use the urn:ietf:params:oauth:grant-type:jwt-bearer
Authorization Grant, the client must make an OAuth 2.0 access token
request using the following specific parameter values and encodings:
grant_type
value must beurn:ietf:params:oauth:grant-type:jwt-bearer
.- The
assertion
parameter must contain a single JWT.
You can also add the scope
parameter to indicate the requested scope:
POST /oauth2/token HTTP/1.1
Host: {project.slug}.projects.oryapis.com
Content-Type: application/x-www-form-urlencoded
scope=foo+bar
&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...].
J9l-ZhwP[...]
Establishing a trust relationship
Before using this grant type, a trust relationship must be established in Ory OAuth2 and OpenID Connect. There are two types of trust relationships:
- Trust relationships restricted to a single subject. This means that the issuer is only allowed to sign JWTs for the trusted subject.
- Trust relationships that allow issuing tokens for any subject. While this can be useful to implement a secure token service, it gives the issuer the ability to impersonate any user. As such, this relationship should only be established if the issuer is as trusted as your own Ory OAuth2 and OpenID Connect instance.
Restricted trust relationships require registering the issuer, subject, and the public key using the Ory SDK:
import { Configuration, OAuth2Api } from "@ory/client"
const ory = new OAuth2Api(
new Configuration({
basePath: `https://${process.env.ORY_PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)
export async function createTrustRelation(accessToken: string) {
const { data } = await ory.trustOAuth2JwtGrantIssuer({
trustOAuth2JwtGrantIssuer: {
// The "allow_any_subject" indicates that the issuer is allowed to have any principal as the subject of the JWT.
allow_any_subject: true,
// The "subject" identifies the principal that is the subject of the JWT. It must be unset if `allow_any_subject` is true.
subject: "some-subject-id",
// The "expires_at" indicates, when grant will expire, so we will reject assertion from "issuer" targeting "subject".
expires_at: "2021-04-23T18:25:43.511Z",
// The "issuer" identifies the principal that issued the JWT assertion (same as "iss" claim in JWT).
issuer: "https://example.org",
// The public key with which the JWT's signature can be verified (example)
jwk: {
alg: "RS256",
use: "sig",
kty: "RSA",
e: "AQAB",
kid: "d8e91f55-67e0-4e56-a066-6a5f0c2efdf7",
n: "nzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA-kzeVOVpVWwkWdVha4s38XM_pa_yr47av7-z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr_Mrm_YtjCZVWgaOYIhwrXwKLqPr_11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e-lf4s4OxQawWD79J9_5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa-GSYOD2QU68Mb59oSk2OB-BtOLpJofmbGEGgvmwyCI9Mw",
},
// The "scope" contains list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749])
scope: ["read"],
},
})
}
::tip
You can also delete, get, and list trust relationships.
:::
Exchanging JWT assertion for access token
To exchange a JWT for an access token at the OAuth2 token endpoint, you send a POST request to the OAuth2 token endpoint with the following parameters:
grant_type
: set tourn:ietf:params:oauth:grant-type:jwt-bearer
assertion
: the JWT, which should be passed in the request body or in theassertion
parameter of the request body.
- Sample call
- JWT used
- Scopes in JWT
This sample request exchanges a JWT for an OAuth2 access token:
The scope
parameter is optional.
POST /oauth2/token HTTP/1.1
Host: {project.slug}.projects.oryapis.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&scope=read
&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6IjcxNDZkZDBiLWYyNDMtNDNiYS04MTVjLTdhMDAyMTZiNDgyMyIsIm5iZiI6MTMwMDgxNTc4MCwiZXhwIjoxMzAwODE5MzgwfQ.Dpn7zYEhaWxi7CLxr1c8Db2zxOJDzpu5QTZgeM6me68aGt7jgpKujunfx2FBhhuKY2oJmIAhXJWXplGH2NnbCGxNzx17Y4CPGJE9jLC2ZxprvV_5Cdmx5GkGcFjpOXsgBSonhmsyKkxYhS3C-mq4u2Tx9Zi494G2EbDH0L2BSuWYi411qm4LrIHQRdiFP9v34VH-5hU005bvrlGJBA9W-Eom4krFYtC4_Zgc7XY2mcChBw0AYz3A1B0_7ui95iDR-33D5tBAGRn6iGgnVBeR1GmZX5y4jz7Nht2lbPQkrCyLsoPxn2ZQPqvbOUKxdgsrhkcs0UGND8GsDwDzISuuAw
This is the JWT used in the sample call:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6IjcxNDZkZDBiLWYyNDMtNDNiYS04MTVjLTdhMDAyMTZiNDgyMyIsIm5iZiI6MTMwMDgxNTc4MCwiZXhwIjoxMzAwODE5MzgwfQ.Dpn7zYEhaWxi7CLxr1c8Db2zxOJDzpu5QTZgeM6me68aGt7jgpKujunfx2FBhhuKY2oJmIAhXJWXplGH2NnbCGxNzx17Y4CPGJE9jLC2ZxprvV_5Cdmx5GkGcFjpOXsgBSonhmsyKkxYhS3C-mq4u2Tx9Zi494G2EbDH0L2BSuWYi411qm4LrIHQRdiFP9v34VH-5hU005bvrlGJBA9W-Eom4krFYtC4_Zgc7XY2mcChBw0AYz3A1B0_7ui95iDR-33D5tBAGRn6iGgnVBeR1GmZX5y4jz7Nht2lbPQkrCyLsoPxn2ZQPqvbOUKxdgsrhkcs0UGND8GsDwDzISuuAw
The JWT has the following scopes:
{
iss: "https://my-issuer.com",
sub: "7146dd0b-f243-43ba-815c-7a00216b4823",
aud: "https://{project.slug}.projects.oryapis.com/oauth2/token",
nbf: 1300815780,
exp: 1300819380,
}
In the example above, the JWT is passed in the assertion
parameter of the request body. The grant_type
parameter is set to
urn:ietf:params:oauth:grant-type:jwt-bearer
to indicate the exchange of a JWT for an access token.
The OAuth2 token endpoint will then verify the signature of the JWT and check the claims to ensure that it is valid. If the JWT is valid, the OAuth2 token endpoint will issue an access token that can be used to access protected resources:
{
iss: "https://{project.slug}.projects.oryapis.com/",
sub: "7146dd0b-f243-43ba-815c-7a00216b4823",
scp: ["read"],
// ...
}
JWT assertion validation requirements
When using the urn:ietf:params:oauth:grant-type:jwt-bearer
Authorization Grant, the assertion parameter's JWT is validated
through the following steps:
- The JWT must contain an
iss
(issuer) claim that has a unique identifier for the entity that issued the JWT. This value should match theissuer
value of the trust relationship. - The JWT must contain a
sub
(subject) claim that identifies the principal as the subject of the JWT (e.g., the user ID). This value should match thesubject
value of the trust relationship unlessallow_any_subject
istrue
. - The JWT must contain an
aud
(audience) claim with a value that identifies the authorization server as an intended audience. The value should be the OAuth2 Token URL. - The JWT must contain an
exp
(expiration time) claim that restricts the time window during which the JWT can be used. This can be controlled through the/oauth2/grant/jwt/max_ttl
setting. - The JWT may contain an
nbf
(not before) claim that identifies the time before which the token must not be accepted for processing by Ory. - The JWT may contain an
iat
(issued at) claim that identifies the time at which the JWT was issued. This can be controlled through the/oauth2/grant/jwt/iat_optional
(defaultfalse
) setting. If iat is not provided, the current time (when Ory receives the assertion) will be considered the issued date. - The JWT may contain a
jti
(JWT ID) claim that provides a unique identifier for the token. This can be controlled through the/oauth2/grant/jwt/jti_optional
(defaultfalse
) setting. Note that if jti is required, Ory will reject all assertions with the samejti
ifjti
was already used by some assertion, and this assertion is not expired yet (seeexp
claim). - The JWT signature is verified using the JSON Web Key registered in the trust relationship.
- If a scope was included in the OAuth2 access token request, it will be validated against the allowed scope in the trust relationship.
Configuring JWT validation parameters
To configure the /oauth2/grant/jwt/jti_optional
, /oauth2/grant/jwt/iat_optional
, and /oauth2/grant/jwt/max_ttl
settings
using the Ory CLI, follow the example below:
ory patch oauth2-config \
--replace "/oauth2/grant/jwt/jti_optional=true" \
--replace "/oauth2/grant/jwt/iat_optional=true" \
--replace "/oauth2/grant/jwt/max_ttl=1h"
JWTs for client authentication
Ory provides support for OAuth 2.0 Client Authentication with RSA and ECDSA private/public key pairs and supports signing
algorithms such as RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, and EdDSA. This method of authentication
replaces the classic HTTP Basic Authorization and HTTP POST Authorization schemes. Instead of sending the client_id
and
client_secret
, you authenticate the client with a signed JSON Web Token.
To use this feature for a specific OAuth 2.0 Client, set the token_endpoint_auth_method
to private_key_jwt
and register the
public key of the RSA/ECDSA signing key either using the jwks_uri
or jwks
fields of the client.
When authenticating the client at the token endpoint, generate and sign (with the RSA/ECDSA private key) a JSON Web Token with the following claims:
iss
: REQUIRED. Issuer. This claim MUST contain the client_id of the OAuth Client.sub
: REQUIRED. Subject. This claim MUST contain the client_id of the OAuth Client.aud
: REQUIRED. Audience. Theaud
(audience) Claim is a value that identifies the Authorization Server (Ory) as an intended audience. The Authorization Server MUST verify that it's an intended audience for the token. The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.jti
: REQUIRED. JWT ID. This is a unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification.exp
: REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.iat
: OPTIONAL. Time at which the JWT was issued.
To make a request to the /oauth2/token
endpoint, include
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
and
client_assertion=<signed-jwt>
in the request body:
POST /oauth2/token HTTP/1.1
Host: {project.slug}.projects.oryapis.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=i1WsRn1uB1&
client_id=s6BhdRkqt3&
client_assertion_type=
urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
client_assertion=PHNhbWxwOl ... ZT
Registering the client's public key
To configure the public key for an OAuth2 client in order to verify the signature of the authentication JWT, you need to do the following:
- Generate an RSA or ECDSA key pair. The private key will be used by the client to sign JWTs, and the public key will be used by the authorization server to verify the signatures.
- Register the public key with the OAuth2 client. This can be done using the
jwks_uri
orjwks
fields of the client. Thejwks_uri
is a URL that points to a JSON Web Key Set (JWKS) that contains the public key. Thejwks
field is a direct JSON object representation of the JWKS.
Here is an example of how to register an RSA public key for an OAuth2 client:
import { Configuration, OAuth2Api } from "@ory/client"
const ory = new OAuth2Api(
new Configuration({
basePath: `https://${process.env.ORY_PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)
export async function createOAuth2Client() {
await ory.createOAuth2Client({
oAuth2Client: {
token_endpoint_auth_method: "private_key_jwt",
token_endpoint_auth_signing_alg: "RS256", // or ES256, EdDSA; ...
// ...
// define the public key directly:
jwks: {
keys: [
{
kty: "RSA",
n: "jL7h5wc-yeMUsHGJHc0xe9SbTdaLKXMHvcIHQck20Ji7SvrHPdTDQTvZtTDS_wJYbeShcCrliHvbJRSZhtEe0mPJpyWg3O_HkKy6_SyHepLK-_BR7HfcXYB6pVJCG3BW-lVMY7gl5sULFA74kNZH50h8hdmyWC9JgOHn0n3YLdaxSWlhctuwNPSwqwzY4qtN7_CZub81SXWpKiwj4UpyB10b8rM8qn35FS1hfsaFCVi0gQpd4vFDgFyqqpmiwq8oMr8RZ2mf0NMKCP3RXnMhy9Yq8O7lgG2t6g1g9noWbzZDUZNc54tv4WGFJ_rJZRz0jE_GR6v5sdqsDTdjFquPlQ",
e: "AQAB",
use: "sig",
kid: "some-key-id",
},
],
},
// or alternatively tell Ory to fetch it from an URL:
jwks_uri: "https://path-to-my-public/keys.json",
},
})
}
Note that the kid
field (key ID) should be a unique identifier for the public key. This is used by the authorization server to
identify which key to use to verify the signature of the JWT. If you have multiple keys, you should use a different kid
for each
key.
Once the public key has been registered, the OAuth2 client can use its private key to sign JWTs, and the authorization server can use the public key to verify the signatures.