JSON Web Token (JWT) Profile (RFC7523)
Ory Hydra is capable of performing the JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants. This guide defines how a JWT Bearer Token can be used to request an access token when a client wishes to utilize an existing trust relationship, expressed through the semantics of the JWT, without a direct user-approval step at the authorization server (Hydra).
Ory Hydra supports both methods expressed in RFC 7523:
- Using JWTs as Authorization Grants: Allows exchanging a JSON Web Token for an Access Token.
- Using JWTs for Client Authentication: Allows OAuth 2.0 Client Authentication using public/private keys via JSON Web Tokens.
Exchanging JWTs for Access Tokens​
To use the Authorization Grant urn:ietf:params:oauth:grant-type:jwt-bearer
,
the client performs an OAuth 2.0 Access Token Request as defined in
Section 4.1 of the OAuth Assertion Framework RFC7521
with the following specific parameter values and encodings:
- The value of the
grant_type
isurn:ietf:params:oauth:grant-type:jwt-bearer
. - The value of the
assertion
parameter MUST contain a single JWT.
The scope
parameter may be used, as defined in the OAuth Assertion Framework
RFC7521, to indicate the
requested scope:
POST /oauth2/token HTTP/1.1
Host: public.hydra.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
Clients using this grant must be authenticated.
Establishing a Trust Relationship​
Before using this grant type, you must establish a trust relationship in Ory Hydra. This involves registering the issuer, subject, and the public key at Ory Hydra's Admin Endpoint:
POST https://<admin.ory-hydra>/trust/grants/jwt-bearer/issuers
Content-Type: application/json
{
// The issuer you want to trust.
"issuer": "https://my-issuer.com",
// The "sub" field of the access token to be created.
"subject": "alice@example.org",
// The allowed scope of the generated access token.
"scope": ["read"],
// The public key with which the JWT Bearer's signature can be verified.
"jwk": {
"kty":"RSA",
"e":"AQAB",
"kid":"d8e91f55-67e0-4e56-a066-6a5f0c2efdf7",
"n":"nzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA-kzeVOVpVWwkWdVha4s38XM_pa_yr47av7-z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr_Mrm_YtjCZVWgaOYIhwrXwKLqPr_11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e-lf4s4OxQawWD79J9_5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa-GSYOD2QU68Mb59oSk2OB-BtOLpJofmbGEGgvmwyCI9Mw"
}
// When this trust relationship expires.
"expires_at": "2021-04-23T18:25:43.511Z",
}
The above example would allow the following JWT Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6Imh0dHBzOi8vcHVibGljLmh5ZHJhLmNvbS9vYXV0aDIvdG9rZW4iLCJuYmYiOjEzMDA4MTU3ODAsImV4cCI6MTMwMDgxOTM4MH0.baBiLrVRRU1AKYvAn0X4eIeLvFfpe2wsoD3VTMtaYdbEW2-w-SFeGeEzl5B6sh612bfKkoeihhFVx2md7DP-Rl5asicJzeIhcPETzZbVSPxR1lFdOBwcIPG5N70aSJs2zSn3jnRIhpZf85YZOI8RbQ93Kxla741_4xruHbsNRFqIuWVhxk95BCCnoXzEd8vBTxd_GMn9VijUY_piLPMo-OifRF9pSjYo38aJmRW1tJzeFCMruc9X1W-2c-L_t3rV7zYBH3LlpDZfwyy3T5Pmqf6QKeq1N-MjLnIJcZGT89jqxLmqVFRvAiEyA6iMQXVxmENOnwylGPwuR8DewhWMqg
which has the claims
{
iss: 'https://my-issuer.com',
sub: 'alice@example.org',
aud: 'https://public.hydra.com/oauth2/token',
nbf: 1300815780,
exp: 1300819380
}
to be exchanged for an OAuth2 Access Token (the scope
parameter is optional!)
POST /oauth2/token HTTP/1.1
Host: public.hydra.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&scope=read
&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6Imh0dHBzOi8vcHVibGljLmh5ZHJhLmNvbS9vYXV0aDIvdG9rZW4iLCJuYmYiOjEzMDA4MTU3ODAsImV4cCI6MTMwMDgxOTM4MH0.baBiLrVRRU1AKYvAn0X4eIeLvFfpe2wsoD3VTMtaYdbEW2-w-SFeGeEzl5B6sh612bfKkoeihhFVx2md7DP-Rl5asicJzeIhcPETzZbVSPxR1lFdOBwcIPG5N70aSJs2zSn3jnRIhpZf85YZOI8RbQ93Kxla741_4xruHbsNRFqIuWVhxk95BCCnoXzEd8vBTxd_GMn9VijUY_piLPMo-OifRF9pSjYo38aJmRW1tJzeFCMruc9X1W-2c-L_t3rV7zYBH3LlpDZfwyy3T5Pmqf6QKeq1N-MjLnIJcZGT89jqxLmqVFRvAiEyA6iMQXVxmENOnwylGPwuR8DewhWMqg
with resulting access token claims:
{
"iss": "https://public.hydra.com/",
"sub": "alice@example.org",
"scp": ["read"],
// ...
}
You can also delete, get, and list trust relationships. Please check the HTTP REST API documentation for more details.
OAuth2 JWT Bearer Grant Type Validation​
When performing the urn:ietf:params:oauth:grant-type:jwt-bearer
Authorization
Grant, the JWT Bearer in the assertion
parameter is validated as follows:
- The JWT MUST contain an
iss
(issuer) claim that contains a unique identifier for the entity that issued the JWT. The value must match theissuer
value of the trust relationship. - The JWT MUST contain a
sub
(subject) claim identifying the principal that is the subject of the JWT (e.g. user ID). The value must match thesubject
value of the trust relationship. - The JWT MUST contain an
aud
(audience) claim containing a value that identifies the authorization server (Hydra) as an intended audience. So this value must be Hydra Token URL. - The JWT MUST contain an
exp
(expiration time) claim that limits the time window during which the JWT can be used. Can be controlled byoauth2.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 Hydra. - The JWT MAY contain an
iat
(issued at) claim that identifies the time at which the JWT was issued. Controlled byoauth2.grant.jwt.iat_optional
(defaultfalse
) Ifiat
is not passed, then current time (when assertion is received by Hydra) will be considered as issued date. - The JWT MAY contain a
jti
(JWT ID) claim that provides a unique identifier for the token. Controlled byoauth2.grant.jwt.jti_optional
(defaultfalse
) setting. Note: Ifjti
is configured to be required, then Hydra 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 MUST be digitally signed.
If a scope was included in the OAuth2 Access Token Request
POST /oauth2/token HTTP/1.1
Host: public.hydra.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&scope=read
&assertion=...
Hydra will check them against scopes defined in the corresponding trust relationship.
Using JWTs for Client Authentication​
Ory Hydra supports OAuth 2.0 Client Authentication with RSA and ECDSA private/public keypairs with currently supported signing algorithms:
- RS256 (default), RS384, RS512
- PS256, PS384, PS512
- ES256, ES384, ES512
- EdDSA
This authentication method 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 enable this feature for a specific OAuth 2.0 Client, you must set
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, you generate and sign (with the RSA/ECDSA private key) a JSON Web Token with the following claims:
iss
: REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client.sub
: REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.aud
: REQUIRED. Audience. The aud (audience) Claim. Value that identifies the Authorization Server (Ory Hydra) as an intended audience. The Authorization Server MUST verify that it is an intended audience for the token. The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.jti
: REQUIRED. JWT ID. 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.
When making a request to the /oauth2/token
endpoint, you 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: my-hydra.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
Here's what a client with a jwks
containing one RSA public key looks like:
{
"client_id": "rsa-client-jwks",
"jwks": {
"keys": [
{
"kty": "RSA",
"n": "jL7h5wc-yeMUsHGJHc0xe9SbTdaLKXMHvcIHQck20Ji7SvrHPdTDQTvZtTDS_wJYbeShcCrliHvbJRSZhtEe0mPJpyWg3O_HkKy6_SyHepLK-_BR7HfcXYB6pVJCG3BW-lVMY7gl5sULFA74kNZH50h8hdmyWC9JgOHn0n3YLdaxSWlhctuwNPSwqwzY4qtN7_CZub81SXWpKiwj4UpyB10b8rM8qn35FS1hfsaFCVi0gQpd4vFDgFyqqpmiwq8oMr8RZ2mf0NMKCP3RXnMhy9Yq8O7lgG2t6g1g9noWbzZDUZNc54tv4WGFJ_rJZRz0jE_GR6v5sdqsDTdjFquPlQ",
"e": "AQAB",
"use": "sig",
"kid": "rsa-jwk"
}
]
},
"token_endpoint_auth_method": "private_key_jwt",
"token_endpoint_auth_signing_alg": "RS256"
}
And here is how it looks like for a jwks
including an ECDSA public key:
{
"client_id": "ecdsa-client-jwks",
"jwks": {
"keys": [
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"kid": "ecdsa-jwk",
"x": "nQjdhpecjZRlworpYk_TJAQBe4QbS8IwHY1DWkfR0w0",
"y": "UQfLzHxhc4i3EETUeaAS1vDVFJ-Y01hIESiXqqS86Vc"
}
]
},
"token_endpoint_auth_method": "private_key_jwt",
"token_endpoint_auth_signing_alg": "ES256"
}
And here is how it looks like for a jwks
including an EdDSA public key:
{
"client_id": "eddsa-client-jwks",
"jwks": {
"keys": [
{
"kty": "OKP",
"use": "sig",
"crv": "Ed25519",
"kid": "eddsa-jwk",
"x": "cg1qGqQGSF6xvzoDZVaDfxu0c2fPhUEuVHYUr1WYVXs"
}
]
},
"token_endpoint_auth_method": "private_key_jwt",
"token_endpoint_auth_signing_alg": "EdDSA"
}
And with jwks_uri
:
{
"client_id": "client-jwks-uri",
"jwks_uri": "http://path-to-my-public/keys.json",
"token_endpoint_auth_method": "private_key_jwt",
"token_endpoint_auth_signing_alg": "RS256"
}
The jwks_uri
must return a JSON object containing the public keys associated
with the OAuth 2.0 Client:
{
"keys": [
{
"kty": "RSA",
"n": "jL7h5wc-yeMUsHGJHc0xe9SbTdaLKXMHvcIHQck20Ji7SvrHPdTDQTvZtTDS_wJYbeShcCrliHvbJRSZhtEe0mPJpyWg3O_HkKy6_SyHepLK-_BR7HfcXYB6pVJCG3BW-lVMY7gl5sULFA74kNZH50h8hdmyWC9JgOHn0n3YLdaxSWlhctuwNPSwqwzY4qtN7_CZub81SXWpKiwj4UpyB10b8rM8qn35FS1hfsaFCVi0gQpd4vFDgFyqqpmiwq8oMr8RZ2mf0NMKCP3RXnMhy9Yq8O7lgG2t6g1g9noWbzZDUZNc54tv4WGFJ_rJZRz0jE_GR6v5sdqsDTdjFquPlQ",
"e": "AQAB",
"use": "sig",
"kid": "rsa-jwk"
}
]
}