Document updated on May 23, 2023
Namespace | auth/validator |
---|---|
Log prefix | [ENDPOINT: /foo][JWTValidator] |
Scope | endpoint |
Source | krakend/krakend-jose |
Protect endpoints from public usage by validating JWT tokens generated by any industry-standard OpenID Connect (OIDC) integration.
Before digging any further, here are some answers to frequently asked questions:
KrakenD does not generate the tokens itself. Still, you can plug it into any SaaS or self-hosted OpenID Identity Provider (IdP) using industry standards (e.g., Auth0, Azure AD, Google Firebase, Keycloak, etc.)
KrakenD does not need to validate all calls using your IdP. KrakenD validates every incoming call’s signature and it doesn’t make token introspection (asking for the IdP data about the token owner).
If you don’t have an identity server, you can still use your classic monolith/backend login system and adapt it to return a JWT payload (a simple JSON). From here, let KrakenD sign the token for you and start using tokens immediately.
Your self-hosted identity server doesn’t need to be exposed to the Internet, as it can live behind KrakenD and let the token generation requests be proxied through KrakenD.
I have tokens generated by the provider XYZ, are they supported? Yes. We still need to find a single major provider that doesn’t follow the JSON Web Encryption (RFC 7516), JSON Web Signature (RFC 7515), or JSON Web Token (RFC 7519) specifications.
If you are new to JWT validation, start reading the JSON Web Tokens overview
When KrakenD decodes the base64
token string passed in the Bearer
or a cookie, it expects to find in its header section the following fields:
{
"alg": "RS256",
"kid": "MDNGMjU2M0U3RERFQUEwOUUzQUMwQ0NBN0Y1RUY0OEIxNTRDM0IxMw"
}
The alg
and kid
values depend on your implementation, but they must be present.
Make sure you are declaring the right kid
in your JWT. Paste a token in a debugger to find out.
The value provided in the kid
must match with the kid
declared at the jwk_url
or jwk_local_path
.
The example above used this public key. Notice how the kid
matches the single key present in the JWK document and the token header.
KrakenD is built with security in mind and uses JWS (instead of plain JWT or JWE), and the kid
points to the right key in the JWS. This is why this entry is mandatory to validate your tokens.
The JWT validation must be present inside every endpoint definition needing it. If several endpoints are going to require JWT validation, consider using the flexible configuration to avoid repetitive declarations.
Enable the JWT validation by adding the namespace "auth/validator"
inside the extra_config
of the desired endpoint
. Do not forget to add caching.
For instance, to protect the endpoint /protected/resource
:
{
"endpoint": "/protected/resource",
"extra_config": {
"auth/validator": {
"alg": "RS256",
"audience": ["http://api.example.com"],
"roles_key": "http://api.example.com/custom/roles",
"roles": ["user", "admin"],
"jwk_url": "https://albert-test.auth0.com/.well-known/jwks.json",
"cache": true
}
},
"backend": [
{
"url_pattern": "/"
}
]
}
This configuration makes sure that:
user
or admin
(taken from a key in the JWT payload named http://api.example.com/custom/roles
)The following settings are available for JWT validation. There are many options, although generally only the fields alg
and jwk_url
or jwk_local_path
are mandatory, and the rest of the keys can be added or not at your best convenience or depending on other options.
These options are for the extra_config
’s namespace "auth/validator"
placed in every endpoint (use flexible configuration to avoid code repetition):
alg
+
jwk_local_path
, or
alg
+
jwk_url
| The hashing algorithm used by the token issuer. Possible values are: "EdDSA" , "HS256" , "HS384" , "HS512" , "RS256" , "RS384" , "RS512" , "ES256" , "ES384" , "ES512" , "PS256" , "PS384" , "PS512" Defaults to "RS256" |
| Reject tokens that do not contain ALL audiences declared in the list. Example: "audience1" |
| Set this value to true (recommended) to stop downloading keys on every request and store them in memory for the next cache_duration period and avoid hammering the key server, as recommended for performance. Do not use this flag when using jwk_local_ca .Defaults to false |
| The cache duration in seconds when the cache is enabled. 15 minutes when unset.Defaults to 900 |
| Override the default cipher suites. Use it if you want to enforce an even higher security standard. Defaults to [49199,49195,49200,49196,52392,52393] |
| Add the key name of the cookie containing the token when it is not passed in the headers Example: "cookie_jwt" |
| The cyphering key. |
| When true, disables security of the JWK client and allows insecure connections (plain HTTP) to download the keys. Useful for development environments. Defaults to false |
| When set, tokens not matching the issuer are rejected. Example: "issuer" |
| A list of fingerprints (the certificate’s unique identifier) for certificate pinning and avoid man-in-the-middle attacks. Add fingerprints in base64 format. |
| Path to the CA’s certificate verifying a secure connection when downloading the JWK. Use when not recognized by the system (e.g., self-signed certificates). |
| Local path to the JWK public keys, has preference over jwk_url . Instead of pointing to an external URL (with jwk_url ), public keys are kept locally, in a plain JWK file (security alert!), or encrypted. When encrypted, also add secret_url and cypher_key .Example: "./jwk.txt" |
| The URL to the JWK endpoint with the public keys used to verify the token’s authenticity and integrity. Use with cache to avoid re-downloading the key on every request. Consider enabling shared caching too. The identity server will receive an HTTP(s) request from KrakenD with a KrakenD user agent, and the identity server must reply with a JSON object and a content-type application/jwk-set+json or application/json .Examples: "https://some-domain.auth0.com/.well-known/jwks.json" , "http://KEYCLOAK:8080/auth/realms/master/protocol/openid-connect/certs" , "https://yourOktaBaseUrl/v1/keys" |
| Allows strategies other than kid to load keys.Possible values are: "kid" , "x5t" , "kid_x5t" |
| When true , any JWT validation operation gets printed in the log with a level ERROR . You will see if a client does not have sufficient roles, the allowed claims, scopes, and other useful information.Defaults to false |
| Enables passing claims in the backend’s request header. You can pass nested claims using the dot . operator. E.g.: realm_access.roles . |
| When set, the JWT token not having at least one of the listed roles is rejected. |
| When validating users through roles, provide the key name inside the JWT payload that lists their roles. If this key is nested inside another object, add roles_key_is_nested and use the dot notation . to traverse each level. E.g.: resource_access.myclient.roles represents the payload {resource_access: { myclient: { roles: ["myrole"] } } . Notice that the roles object you choose is a list, not a map.Example: "resource_access.myclient.roles" |
| If the roles key uses a nested object using the . dot notation, you must set it to true to traverse the object. |
| A list of scopes to validate. Make sure to use a list [] in the config, but when passing the token, the scopes should be separated by spaces, e.g.: "my_scopes": "resource1:action1 resource3:action7" . |
| The key name where KrakenD can find the scopes. The key can be a nested object using the . dot notation, e.g.: data.access.my_scopes . |
| Defines if the user needs to have in its token at least one of the listed claims (any ), or all of them.Possible values are: "any" , "all" Defaults to "any" |
| An URL with a custom scheme using one of the supported providers (e.g.: awskms://keyID ) (see providers).Examples: "base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4=" , "awskms://keyID" , "azurekeyvault://keyID" , "gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]" , "hashivault://keyID" |
Here there is an example using an external jwk_url
:
{
"endpoint": "/foo",
"extra_config": {
"auth/validator": {
"alg": "RS256",
"jwk_url": "https://url/to/jwks.json",
"cache": true,
"audience": [
"audience1"
],
"roles_key": "department",
"roles_key_is_nested": false,
"roles": [
"sales",
"development"
],
"scopes_key": "my_scopes",
"scopes_matcher": "any",
"scopes": [
"resource1:action1",
"resource2:action1",
"resource1:action2"
],
"issuer": "http://my.api.com",
"cookie_key": "TOKEN",
"disable_jwk_security": true,
"jwk_fingerprints": [
"S3Jha2VuRCBpcyB0aGUgYmVzdCBnYXRld2F5LCBhbmQgeW91IGtub3cgaXQ=="
],
"cipher_suites": [
10, 47, 53
],
"operation_debug": true
}
}
}
RS512
, make sure your KrakenD instances have a proper CPU setting. Additionally, enable cache
to avoid hammering your identity servers and save internal network traffic.A crucial decision when using JWT is defining a caching strategy that works with your key rotation policy.
If you don’t set any caching option, KrakenD will default to the more pessimistic scenario where you rotate the keys continuously, and KrakenD needs to download the keys in the JWK URL every single time. In most cases, this option will add a lot of pressure to your identity server. Therefore, we encourage you to enable cache
in each endpoint, and even better if you add the second-level shared cache in the JWK client with shared_cache_duration
. For instance:
{
"version": 3,
"extra_config": {
"auth/jwk-client": {
"@comment": "Enable a JWK shared cache amongst all endpoints of 15 minutes",
"shared_cache_duration": 900
}
},
"endpoints": [{
"endpoint": "/protected",
"extra_config": {
"auth/validator": {
"cache": true,
"cache_duration": 3600,
"@comment": "Rest of the validator options omitted for simplicity"
}
}
}]
}
KrakenD does the following validation to let users hit protected endpoints:
jwk_url
must be accessible by KrakenD at all times (caching is recommended)kid
in the header is listed in the jwk_url
or jwk_local_path
.k
) is base64 urlencodedalg
is supported by KrakenD and matches exactly the one used in the endpoint definition.issuer
matches (if present in the configuration), and is a stringaudience
matches (if present in the configuration), and is a stringroles
(if present in the configuration))The configuration allows you to define the set of required roles. For example, a user who passes a token with roles A
and B
, can access an endpoint requiring "roles": ["A","C"]
as it has one of the required options (A
).
If the token is expired, the configuration inside the namespace is incorrect, the signature doesn’t match, the required claims do not match, or the token is revoked, a 401 Unauthorized
is returned.
When the token doesn’t include the defined ACL’s required roles, a 403 Forbidden
is returned.
When you generate tokens for end-users, make sure to set a low expiration. Tokens are supposed to have short lives and should expire in a few minutes or hours.
When using a jwk_local_path
, the secret_url
scheme accepts different providers:
The local secrets require an URL with the following scheme:
base64key://base64UrlEncondedContent
The URL host must be base64 url encoded
and must decode to exactly 32 bytes. Here is an example of the extra_config
:
{
"jwk_local_path":"./jwk.txt",
"secret_url":"base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4=",
"cypher_key":"gCERmfqHMoEu3+utqBa/R1oMZYIvh0OOKtJmnX/hDPDxbXCGXGvO3SF7B5FWxrJnRW7rnjGIV4eP2VLrYX2q9pJM49BpP+A9"
}
This config will use the key smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4=
for decrypting de cypher_key
(base64 standard enconded) and then decrypting the content of the file ./jwt.txt
.
See this test to understand how to generate and encrypt payloads.
awskms://keyID
The URL Host + Path is used as the key ID, which can be an Amazon Resource Name (ARN), alias name, or alias ARN. Note that ARNs may contain “:” characters, which cannot be escaped in the Host part of a URL, so you should use the awskms:///<ARN>
form.
More information about AWS KMS
azurekeyvault://keyID
The credentials are taken from the environment unless the AZURE_KEYVAULT_AUTH_VIA_CLI
environment variable is set to true, in which case it uses the az
command line.
More information about Azure Key Vault
gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]
You can take the URL from the GCP console.
hashivault://keyID
Environment variables VAULT_SERVER_URL
and VAULT_SERVER_TOKEN
are used.
Since KrakenD 1.2.0, it is possible to use data present in the claims to inject it into the backend’s final URL. The notation of the url_pattern
field includes the parsing of {JWT.some_claim}
, where some_claim
is an attribute of your claim.
For instance, when your JWT payload is represented by something like this:
{
"sub": "1234567890",
"name": "Mr. KrakenD"
}
Having a backend
defined with:
{
"url_pattern": "/foo/{JWT.sub}",
"method": "POST"
}
The call to your backend would produce the request:
POST /foo/1234567890
Keep in mind that this syntax in the url_pattern
field is only available if the backend loads the extra_config "auth/validator"
and that it does not work with nested attributes in the payload.
If KrakenD can’t replace the claim’s content for any reason, the backend receives a request to the literal URL /foo/{JWT.sub}
.
It is possible to forward claims in a JWT as request headers. It is a common use case to have, for instance, the sub claim added as an X-User
header to the request. The propagation makes that other KrakenD components, such as rate-limiting, can work with information in the token.
Important: The endpoint input_headers
needs to be set as well, so the backend can see it.
{
"extra_config": {
"auth/validator": {
"propagate_claims": [
["sub", "x-user"],
["realm_access.role", "x-role"]
]
}
}
}
In this case, the sub
claim’s value will be added as x-user
header to the request. If the claim does not exist, the mapping is just skipped.
In addition, the nested property role
(inside realm_access
) is passed as an x-role
header.
The KrakenD Playground demonstrates how to protect endpoints using JWT and includes two examples ready to use:
To try it, clone the playground and follow the README.
Accepted values for the alg
field are:
EdDSA
: EdDSAHS256
: HMAC using SHA-256HS384
: HMAC using SHA-384HS512
: HMAC using SHA-512RS256
: RSASSA-PKCS-v1.5 using SHA-256RS384
: RSASSA-PKCS-v1.5 using SHA-384RS512
: RSASSA-PKCS-v1.5 using SHA-512ES256
: ECDSA using P-256 and SHA-256ES384
: ECDSA using P-384 and SHA-384ES512
: ECDSA using P-521 and SHA-512PS256
: RSASSA-PSS using SHA256 and MGF1-SHA256PS384
: RSASSA-PSS using SHA384 and MGF1-SHA384PS512
: RSASSA-PSS using SHA512 and MGF1-SHA512Accepted values for cipher suites are:
5
: TLS_RSA_WITH_RC4_128_SHA10
: TLS_RSA_WITH_3DES_EDE_CBC_SHA47
: TLS_RSA_WITH_AES_128_CBC_SHA53
: TLS_RSA_WITH_AES_256_CBC_SHA60
: TLS_RSA_WITH_AES_128_CBC_SHA256156
: TLS_RSA_WITH_AES_128_GCM_SHA256157
: TLS_RSA_WITH_AES_256_GCM_SHA38449159
: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA49161
: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA49162
: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA49169
: TLS_ECDHE_RSA_WITH_RC4_128_SHA49170
: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA49171
: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA49172
: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA49187
: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA25649191
: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256Default suites are:
49199
: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA25649195
: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA25649200
: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA38449196
: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38452392
: TLS_ECDHE_RSA_WITH_CHACHA20_POLY130552393
: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305The documentation is only a piece of the help you can get! Whether you are looking for Open Source or Enterprise support, see more support channels that can help you.