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. Trust relationships come in two flavors:
- 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. This may be useful for some cases (like implementing a Secure Token Service), but gives the issuer the ability to impersonate any user so you should only do this if you trust the issuer as much as you trust your own Ory Hydra instance.
Restricted trust relationships require 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.
// Let's say 7146dd0b-f243-43ba-815c-7a00216b4823 is the user ID of Alice:
"subject": "7146dd0b-f243-43ba-815c-7a00216b4823",
// 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",
}
If you want an issuer to provide tokens for any subject you can omit the subject field and set the allow_any_subject
flag to
true:
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",
// Setting this to true will allow the issuer to provide tokens for any subject.
"allow_any_subject": true,
// 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",
}
Both examples above would allow the following JWT Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6IjcxNDZkZDBiLWYyNDMtNDNiYS04MTVjLTdhMDAyMTZiNDgyMyIsIm5iZiI6MTMwMDgxNTc4MCwiZXhwIjoxMzAwODE5MzgwfQ.Dpn7zYEhaWxi7CLxr1c8Db2zxOJDzpu5QTZgeM6me68aGt7jgpKujunfx2FBhhuKY2oJmIAhXJWXplGH2NnbCGxNzx17Y4CPGJE9jLC2ZxprvV_5Cdmx5GkGcFjpOXsgBSonhmsyKkxYhS3C-mq4u2Tx9Zi494G2EbDH0L2BSuWYi411qm4LrIHQRdiFP9v34VH-5hU005bvrlGJBA9W-Eom4krFYtC4_Zgc7XY2mcChBw0AYz3A1B0_7ui95iDR-33D5tBAGRn6iGgnVBeR1GmZX5y4jz7Nht2lbPQkrCyLsoPxn2ZQPqvbOUKxdgsrhkcs0UGND8GsDwDzISuuAw
which has the claims
{
iss: "https://my-issuer.com",
sub: "7146dd0b-f243-43ba-815c-7a00216b4823",
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.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5jb20iLCJzdWIiOiJhbGljZUBleGFtcGxlLm9yZyIsImF1ZCI6IjcxNDZkZDBiLWYyNDMtNDNiYS04MTVjLTdhMDAyMTZiNDgyMyIsIm5iZiI6MTMwMDgxNTc4MCwiZXhwIjoxMzAwODE5MzgwfQ.Dpn7zYEhaWxi7CLxr1c8Db2zxOJDzpu5QTZgeM6me68aGt7jgpKujunfx2FBhhuKY2oJmIAhXJWXplGH2NnbCGxNzx17Y4CPGJE9jLC2ZxprvV_5Cdmx5GkGcFjpOXsgBSonhmsyKkxYhS3C-mq4u2Tx9Zi494G2EbDH0L2BSuWYi411qm4LrIHQRdiFP9v34VH-5hU005bvrlGJBA9W-Eom4krFYtC4_Zgc7XY2mcChBw0AYz3A1B0_7ui95iDR-33D5tBAGRn6iGgnVBeR1GmZX5y4jz7Nht2lbPQkrCyLsoPxn2ZQPqvbOUKxdgsrhkcs0UGND8GsDwDzISuuAw
with resulting access token claims:
{
"iss": "https://public.hydra.com/",
"sub": "7146dd0b-f243-43ba-815c-7a00216b4823",
"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'ssued 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 (for example the 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
isn't 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 isn't 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 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's 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"
}
]
}