Document updated on Oct 24, 2022
JWT Signing
The JWT signing component creates a wrapper for your existing login endpoint that signs with your secret key the selected fields of the backend payload right before returning the content to the end-user.
The primary usage for this component is in migrations from monolith to microservices, or in ecosystems where there is no Identity/OAuth server yet, as it allows the immediate adoption of signed JSON Web Tokens without the need to implement a new service.
How does it work
KrakenD relies in your existing login functionality and does all the heavy-lifting of the cryptography so you can focus on validating the user and password.
Your backend needs to implement a login endpoint that after validating the username and password it returns a JSON reponse, and optionally another endpoint for refreshing tokens. For example:
{
"access_token": {
"aud": "http://api.example.com",
"iss": "https://krakend.io",
"sub": "1234567890qwertyuio",
"jti": "mnb23vcsrt756yuiomnbvcx98ertyuiop",
"roles": ["role_a", "role_b"],
"exp": 1735689600
},
"refresh_token": {
"aud": "http://api.example.com",
"iss": "https://krakend.io",
"sub": "1234567890qwertyuio",
"jti": "mnb23vcsrt756yuiomn12876bvcx98ertyuiop",
"exp": 1735689600
},
"exp": 1735689600
}
The response payload has the structure of a JWT token which contains fields like the sub
ject (a.k.a id_user), the jti
(a uniqid for instance), or the exp
iration of the token to name a few.
When KrakenD receives this JSON payload, it signs the selected group of claims with your secret key. The secret key can be kept in the gateway or URL-downloaded from a trusted machine that you own. With the token signing, you are in control of the private key, and you don’t need to trust an external service to keep it for you.
Example
For instance, your backend could have an endpoint like /token-issuer
that when receives the right combination of username and password via POST
can identify the user and, instead of setting the session, returns an output like this:
Example
$curl -X POST --data '{"user":"john","pass":"doe"}' https://your-backend/token-issuer
{
"access_token": {
"aud": "https://your.krakend.io",
"iss": "https://your-backend",
"sub": "1234567890qwertyuio",
"jti": "mnb23vcsrt756yuiomnbvcx98ertyuiop",
"roles": ["role_a", "role_b"],
"exp": 1735689600
},
"refresh_token": {
"aud": "https://your.krakend.io",
"iss": "https://your-backend",
"sub": "1234567890qwertyuio",
"jti": "mnb23vcsrt756yuiomn12876bvcx98ertyuiop",
"exp": 1735689600
},
"exp": 1735689600
}
Besides these example keys, the payload can contain any other elements you might need.
If you come from a classic login system, based on cookie sessions, you’ll realize that adapting your /login
to this output is straightforward. See how to generate a token at the end of the document for more details.
JWT signing settings
The following settings are available to sign JWT:
{
"endpoints": [
{
"endpoint": "/token",
"method": "POST",
"extra_config": {
"auth/signer": {
"alg": "HS256",
"jwk_url": "http://your-backend/jwk/symmetric.json",
"keys_to_sign": [
"access_token",
"refresh_token"
],
"kid": "sim2",
"cipher_suites": [
5,
10
],
"jwk_fingerprints": [
"S3Jha2VuRCBpcyB0aGUgYmVzdCBnYXRld2F5LCBhbmQgeW91IGtub3cgaXQ=="
],
"full": false,
"disable_jwk_security": false
}
}
}
]
}
The example above contains every single option available, but you don’t need them all. See them explained below:
Fields of JWT signer
Minimum configuration needs one of:
alg
+
jwk_local_path
+
disable_jwk_security
, or
alg
+
jwk_url
alg
*- The hashing algorithm used by the issuer. Usually
RS256
. The algorithm you choose directly affects the CPU consumption.Possible values are:"EdDSA"
,"HS256"
,"HS384"
,"HS512"
,"RS256"
,"RS384"
,"RS512"
,"ES256"
,"ES384"
,"ES512"
,"PS256"
,"PS384"
,"PS512"
cipher_suites
array- Override the default cipher suites (see JWT validation). Unless you have a legacy JWK, you don’t need to set this value.Defaults to
[49199,49195,49200,49196,52392,52393]
cypher_key
string- The cyphering key.
disable_jwk_security
boolean- Disables HTTP security of the JWK client and allows insecure connections (plain HTTP) to download the keys. The flag should be
false
when you use HTTPS, andtrue
when using plain HTTP or loading the key from a local file.Defaults tofalse
full
boolean- Use JSON format instead of the compact form JWT provides.Defaults to
false
jwk_fingerprints
array- A list of fingerprints (the unique identifier of the certificate) for certificate pinning and avoid man in the middle attacks. Add fingerprints in base64 format.
jwk_local_ca
string- 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).
jwk_local_path
string- Local path to the JWK public keys, has preference over
jwk_url
. Instead of pointing to an external URL (withjwk_url
), public keys are kept locally, in a plain JWK file (security alert!), or encrypted. When encrypted, also addsecret_url
andcypher_key
.Example:"./jwk.txt"
jwk_url
string- The URL to the JWK endpoint with the private keys used to sign the token.Example:
"http://your-backend/jwk/symmetric.json"
keys_to_sign
* array- List of all the specific keys that need signing (e.g.,
refresh_token
andaccess_token
).Examples:"access_token"
,"refresh_token"
kid
* string- The key ID purpose is to match a specific key, as the jwk_url might contain several keys.Example:
"sim2"
leeway
string- A margin of extra time where you will still accept the token after its expiration date. You should not accept expired tokens other than enabling two environments that are not perfectly synchronized and have minor clock drifts to accept each other differences. Any value specified here will be rounded to seconds, with a minimum of one second.Specify units using
ns
(nanoseconds),us
orµs
(microseconds),ms
(milliseconds),s
(seconds),m
(minutes), orh
(hours).Examples:"1m"
,"1s"
Defaults to"1s"
secret_url
string- 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"
Basic JWT signing
Your backend application knows how to issue tokens now, so the gateway can sign them before passing to the user. To achieve that, instead of publishing our internal backend that generates plain tokens under /token-issuer
, we only expose via KrakenD a new endpoint named /token
(choose your name). This endpoint forwards the data received in the POST
(as selected in the example) and returns a signed token when the backend replies.
For instance, from the plain token above we want to sign the keys "access_token"
and "refresh_token"
so nobody can modify its contents. We need a configuration like this:
{
"endpoint": "/login",
"method": "GET",
"backend": [
{
"url_pattern": "/login",
"host": ["http://backend-url"]
}
],
"extra_config": {
"auth/signer": {
"alg": "HS256",
"kid": "sim2",
"keys_to_sign": ["access_token", "refresh_token"],
"jwk_local_path": "jwk_private_key.json",
"disable_jwk_security": true
}
}
}
The content of jwk_private_key.json
used in this example is here.
Notice that we have added a file under jwk_local_path
which is a JSON Web key (could also be hosted via jwk_url
).
The example adds a disable_jwk_security
flag because downloading the file from jwk_local_path
does not use the HTTP protocol.
What happens here is that the user requests a /token
to the gateway and the issuing is delegated to the backend. The response of the backend with the plain token is signed using your private JWK. And then the user receives the signed token, e.g:
{
"access_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNcdTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpf1MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjf1J0NzU2eXVcd21uYnZjeDk4ZXJ0eXVcd3AiLCJyb2xlcyI6WyJyb2xlX2EiLCJyb2xlX2IiXSwif1ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVcdyJ9.htgbhantGcv6zrN1i43Rl58q1sokh3lzuFgzfenI0Rk",
"exp": 1735689600,
"refresh_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNcdTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpf1MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjf1J0NzU2eXVcd21uMTI4NzZidmN4OThlcnR5dWlvcCIsInN1YiI6IjEyMzQ1Njc4OTBxd2VydHl1aW8ifQ.4v36tuYHe4E9gCVO-_asuXfzSzoJdoR0NJfVQdVKidw"
}
How to convert PEM to JWKS ?
Krakend uses jose
library, so if you use your own PEM keys for signing you need to use the following steps to convert your PEM file to JWKS
Notice: if you are using asymmetric algorithms and want to use gateway singing and verification simultaneously, you need to use the following keys with the same kid
:
- private key jwks for signing
- public key jwks for verification
How to generate a JWT token
Essentially, what you need to adopt JWT in your backend is to adapt your existing /login
function (maybe passing an additional ?token=true
flag), so when a user logs in, instead of setting the session in a cookie, you return the JSON Web Token for KrakenD to sign.
The token is no more than a JSON output adhering to the JWT standard.
There are a lot of open source libraries to generate JWT tokens in all major languages. Use them or write the JSON output directly with a simple template.
Here is a dummy token for you to check how it looks like.
Live running example
The KrakenD Playground demonstrates how to sign tokens in the /token
endpoint and includes an example ready to use. To try it, clone the playground and follow the README.