Skip to main content

JWT Configuration

This section describes the JSON Web Token (JWT) configuration options available in RelyAuth.

Disclaimer

Because RelyAuth is inspired by Hasura Auth's concepts, many sections are borrowed from the official Hasura docs.

Basics

Payload Definition

Example JSON Web Token (JWT) payload configuration definition:

auth.yaml
version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
key:
algorithm: HS256
key:
value: ultra-secret-very-secret-super-secret-key
audience: ["myapp-1234", "myapp-6789"]
allowedSkew: 60
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
locations:
x-hasura-custom-variable:
path: sub

As a minimum, either the claimsConfig, tokenLocation, and key values have to be present.

Example Decoded Payload

{
"iat": 1735916718,
"exp": 1796916677,
"claims.jwt.hasura.io": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user", "admin"],
"x-hasura-user-id": "123",
"x-hasura-org-id": "456",
"x-hasura-custom": "custom-value"
}
}

Example Encoded JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MzU5MTY3MTgsImV4cCI6MTc5NjkxNjY3NywiY2xhaW1zLmp3dC5oYXN1cmEuaW8iOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciIsImFkbWluIl0sIngtaGFzdXJhLXVzZXItaWQiOiIxMjMiLCJ4LWhhc3VyYS1vcmctaWQiOiI0NTYiLCJ4LWhhc3VyYS1jdXN0b20iOiJjdXN0b20tdmFsdWUifX0.5bwSMgxsyULY1uhCJxYd-sO35rCdznRCZ4YMLwDD5u8

See here for the JWT debugger of this example JWT token. The signature secret to verify this token with the HS256 algorithm is ultra-secret-very-secret-super-secret-key.

JWT configuration options

claimsConfig

You can specify where the engine should look for the claims within the decoded token with namespace and locations options.

namespace

The namespace option is used when all of JWT claims are present in a single object within the decoded JWT. Our example uses claims.jwt.hasura.io in the Example Decoded Payload.

claimsConfig:
namespace:
claimsFormat: Json
location: '"claims.jwt.hasura.io"'

The location field indicates the location of the namespace object that uses JMESPath string syntax.

The claimsFormat field indicates whether the claims are a regular JSON object or a stringified JSON. The following possible values are allowed: Json, StringifiedJson.

This is required because providers like AWS Cognito only allow strings in the JWT claims.

Example:

If claimsFormat is Json then the JWT claims should look like:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"claims.jwt.hasura.io": {
"x-hasura-allowed-roles": ["editor", "user", "mod"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "1234567890",
"x-hasura-org-id": "123",
"x-hasura-custom": "custom-value"
}
}

If claimsFormat is StringifiedJson then the JWT claims should look like:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"claims.jwt.hasura.io": "{\"x-hasura-allowed-roles\":[\"editor\",\"user\",\"mod\"],\"x-hasura-default-role\":\"user\",\"x-hasura-user-id\":\"1234567890\",\"x-hasura-org-id\":\"123\",\"x-hasura-custom\":\"custom-value\"}"
}

locations

This locations option can be used when JWT claims are not all present in the single object, but individual claims are provided a JMESPath within the decoded JWT. In this option, you can indicate a JMESPath for individual claims and an optional default value if the claim doesn't exist.

Example: JWT config with JSON path values

{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"user": {
"id": "ujdh739kd"
},
"custom_claims": {
"all_roles": ["user", "editor"]
}
}

The mapping for x-hasura-allowed-roles, x-hasura-default-role and x-hasura-user-id session variables can be specified in the locations configuration as follows:

claimsConfig:
locations:
x-hasura-default-role:
path: custom_claims.all_roles[0]
x-hasura-allowed-roles:
path: custom_claims.all_roles
x-hasura-user-id:
path: user.id

Example: JWT config with JSON path values and default values

{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"hasura": {
"all_roles": ["user", "editor"]
}
}
claimsConfig:
locations:
x-hasura-default-role:
path: custom_claims.all_roles[0]
x-hasura-allowed-roles:
path: custom_claims.all_roles
x-hasura-user-id:
path: user.id
default:
value: ujdh739kd
env: USER_ID

In the above case, since the /user/id doesn't exist in the JWT token, the default value of the x-hasura-user-id could be:

  • Value of the environment variable USER_ID if exists.
  • Otherwise, the literal value ujdh739kd will be used.

If the path field is omitted, the x-hasura-user-id claim will automatically use the default value.

claimsConfig:
locations:
x-hasura-default-role:
path: custom_claims.all_roles[0]
x-hasura-allowed-roles:
path: custom_claims.all_roles
x-hasura-user-id:
default:
value: ujdh739kd
Special characters in JMESPath syntax

If the object key contains special characters such as ., -, you need to wrap it with double quotes, for example, "claims.jwt.hasura.io"."x-hasura-allowed-roles".

tokenLocation

Indicates the token location to read the JWT. This setting is an object with the following properties.

  • in: token location the HTTP request. Possible values are header, cookie, and query.
  • name: name of the parameter according to the location.
  • scheme: optional prefix scheme of the token, for example, bearer for the Bearer scheme.

The following are possible options:

In this option, RelyChan expects an Authorization header with the Bearer scheme.

tokenLocation:
in: header
name: Authorization
scheme: bearer

The JWT header should look like:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...

In the cookie mode, RelyChan will try to parse the cookie header with the given cookie name. The value of the cookie should be the exact JWT.

tokenLocation:
type: cookie
name: cookie_name

The JWT token should look like:

Cookie: cookie_name=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...

Query

In this mode, RelyChan expects a query_name parameter in the request URL with the exact JWT token value.

tokenLocation:
type: query
name: query_name

The JWT token should look like:

https://example.com?query_name=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

key

This field specifies the JWT key configuration according to which the incoming JWT will be decoded. You can configure either a fixed algorithm key or a remote JWK URL.

fixed key

In this option, you must indicate a JWT key and its algorithm so the engine can decode and verify the JWT token.

key:
algorithm: HS256
key:
value: ultra-secret-very-secret-super-secret-key
# env: JWT_KEY

The algorithm field specifies the cryptographic signing algorithm which is used to sign the JWTs. Valid values are: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, PS256, PS384, PS512, EdDSA.

The key field can be a literal value or an environment variable name.

  • In the case of a symmetric key (i.e. a HMAC-based key), just the key as is. (e.g. -"abcdef..."). The key must be long enough for the chosen algorithm, (e.g. for HS256 it must be at least 32 characters long).
  • In the case of an asymmetric key (RSA, EdDSA, ECDSA etc.), only the public key, in a PEM-encoded string or as an X509 certificate.

jwkFromUrl

An URL where a provider publishes their JWKs (JSON Web Keys - which are used for signing the JWTs). The URL must publish the JWKs in the standard format as described here.

For example:

  • Auth0 publishes their JWK url at: https://<YOUR_AUTH0_DOMAIN>.auth0.com.
  • Firebase publishes their JWK url at: https://www.googleapis.com/service_accounts/v1/jwk/[email protected].
key:
jwkFromUrl:
value: https://www.googleapis.com/service_accounts/v1/jwk/[email protected]
# env: JWK_URL

The JWTs must be signed by the JWK published at the given URL. They can be signed by any algorithm that is compatible with the key (eg. RS256, RS384, RS512 algorithms require a JWK with an RSA key).

DDN does not currently support rotating JWKs.

audience

This is an optional field. Certain providers might set a claim which indicates the intended audience for the JWT. This must be checked by setting this field.

When this field is set, during the verification process of the JWT, the aud claim in the JWT will be checked to see whether it is equal to the audience field given in the configuration. If they are not equal then the JWT will be rejected.

See the RFC for more details.

This field must be a list of strings.

Examples:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
audience: ["myapp-1234", "myapp-6789"]
# ...
Audience Security Vulnerability

Certain JWT providers share JWKs between multiple tenants. They use the aud claim of the JWT to specify the intended audience. Setting the audience field in the JWT configuration will make sure that the aud claim from the JWT is also checked during verification. Not doing this check will allow JWTs issued for other tenants to be valid as well.

In these cases, you MUST set the audience field to the appropriate value. Failing to do so is a major security vulnerability.

issuer

This is an optional field. It takes a string value.

When this field is set, during the verification process of the JWT, the iss claim in the JWT will be checked to see whether it is equal to the issuer field given in the configuration. If they are not equal then the JWT will be rejected.

See RFC for more details.

Examples:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
# ...
issuer: https://my-auth-server.com

Issuer Notes

  • Certain providers require you to verify the iss claim on the JWT. To do that you can set this field to the appropriate value.
  • A JWT configuration without an issuer will match any issuer field present in an incoming JWT.
  • An incoming JWT without an issuer specified will match a configuration even if it specifies an issuer.

allowed_skew

allowedSkew is an optional field to provide some leeway (to account for clock skews) while comparing the JWT expiry time. This field expects an integer value which will be the number of seconds of the skew value.

JWT Config Examples

HMAC-SHA based

Your auth server is using HMAC-SHA algorithms to sign JWTs, and is using a 256-bit key. In this case, the JWT config will look like:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: HS256
key:
value: ultra-secret-very-secret-super-secret-key

The key is the actual shared secret, which is used by RelyAuth and the external auth server.

RSA based

If your auth server is using the RSA algorithm to sign JWTs, and is using a 512-bit key, the JWT config only needs to have the public key.

Example 1: public key in PEM format (not OpenSSH format):

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: RS512
key:
value:
'-----BEGIN PUBLIC
KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END
PUBLIC KEY-----\n'

Example 2: public key as X509 certificate:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: RS512
key:
value:
'-----BEGIN
CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIINw9gva8BPPIwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgQt7dIsMTIU9k1SUrFviZOGnmHWtIAw\nmtYBcM9I0f9/ka45JIRp5Y1NKpAMFSShs7Wv0m1JS1kXQHdJsPSmjmDKcwnBe3R/\nTU3foRRywR/3AJRM15FNjTqvUm7TeaW16LkkRoECAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBADfY2DEmc2gb8/pqMNWHYq/nTYfJPpK4VA9A0lFTNeoq\nzmnbGwhKj24X+Nw8trsvkrKxHvCI1alDgBaCyzjGGvgOrh8X0wLtymp1yj6PWwee\nR2ZPdUaB62TCzO0iRv7W6o39ey+mU/FyYRtxF0ecxG2a0KNsIyFkciXUAeC5UVDo\nBNp678/SDDx9Ltuxc6h56a/hpBGf9Yzhr0RvYy3DmjBs6eopiGFmjnOKNxQrZ5t2\n339JWR+yiGEAtoHqk/fINMf1An6Rung1xYowrm4guhCIVi5unAvQ89fq0I6mzPg6\nLhTpeP0o+mVYrBmtYVpDpv0e71cfYowSJCCkod/9YbY=\n-----END
CERTIFICATE-----'

Example 3: public key published as JWKs:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
jwkFromUrl:
value: https://www.googleapis.com/service_accounts/v1/jwk/[email protected]

EdDSA based

If your auth server is using EdDSA to sign JWTs, and is using the Ed25519 variant key, the JWT config only needs to have the public key.

Example 1: public key in PEM format (not OpenSSH format):

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: Ed25519
key:
value:
'-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAG9I+toAAJicilbPt36tiC4wi7E1Dp9rMmfnwdKyVXi0=\n-----END PUBLIC
KEY-----'

Example 2: public key as X509 certificate:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: Ed25519
key:
value:
'-----BEGIN CERTIFICATE
REQUEST-----\nMIIBAzCBtgIBADAnMQswCQYDVQQGEwJERTEYMBYGA1UEAwwPd3d3LmV4YW1wbGUu\nY29tMCowBQYDK2VwAyEA/9DV/InajW02Q0tC/tyr9mCSbSnNP1txICXVJrTGKDSg\nXDBaBgkqhkiG9w0BCQ4xTTBLMAsGA1UdDwQEAwIEMDATBgNVHSUEDDAKBggrBgEF\nBQcDATAnBgNVHREEIDAegg93d3cuZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tMAUG\nAytlcANBAKbTqnTyPcf4ZkVuq2tC108pBGY19VgyoI+PP2wD2KaRz4QAO7Bjd+7S\nljyJoN83UDdtdtgb7aFgb611gx9W4go=\n-----END
CERTIFICATE REQUEST-----'

EC based

If your auth server is using ECDSA to sign JWTs, and is using the ES variant with a 256-bit key, the JWT config only needs to have the public key.

Example 1: public key in PEM format (not OpenSSH format):

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: ES256
key:
value:
'-----BEGIN PUBLIC
KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END
PUBLIC KEY-----'

Example 2: public key as X509 certificate:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
algorithm: ES256
key:
value:
'"-----BEGIN
CERTIFICATE-----\nMIIBbjCCARWgAwIBAgIUGn02F6Y6s88dDGmIfwiNxWxDjhswCgYIKoZIzj0EAwIw\nDTELMAkGA1UEBhMCSU4wHhcNMjMwNTI0MTAzNTI4WhcNMjgwNTIyMTAzNTI4WjAN\nMQswCQYDVQQGEwJJTjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBFbP6OfrkG0\n4y93Icpy+MF4FINkfavVFPCOZhKL1H/OkGe5DgSIycKp8w9aJmoHhB1sB3QTugfn\nRWm5nU/TzsajUzBRMB0GA1UdDgQWBBSaqFjzps1qG+x2DPISjaXTWsTOdDAfBgNV\nHSMEGDAWgBSaqFjzps1qG+x2DPISjaXTWsTOdDAPBgNVHRMBAf8EBTADAQH/MAoG\nCCqGSM49BAMCA0cAMEQCIBDHHWa/uLAVdGFEk82auTmw995+MsRwv52VXLw2Z+ji\nAiAXzOWIcGN8p25uhUN/7v9gEcADGIS4yUiv8gsn/Jk2ow==\n-----END
CERTIFICATE-----'

Example 3: public key published as JWKs:

version: v1
kind: RelyAuth
definition:
modes:
- mode: jwt
tokenLocation:
in: header
name: Authorization
scheme: bearer
claimsConfig:
namespace:
location: '"claims.jwt.hasura.io"'
claimsFormat: Json
key:
jwkFromUrl:
value: https://www.gstatic.com/iap/verify/public_key-jwk

Security considerations

Setting audience check

Certain JWT providers share JWKs between multiple tenants (like Firebase). They use the aud claim of JWT to specify the intended tenant for the JWT. Setting the audience field in the JWT configuration will make sure that the aud claim from the JWT is also checked during verification. Not doing this check will allow JWTs issued for other tenants to be valid as well.

In these cases, you MUST set the audience field to appropriate value. Failing to do so is a major security vulnerability.