Skip to main content

Accept signed tokens

A signed token lets a client prove who it is without sending the same fixed key every time. The client's own sign-in service issues a JSON Web Token and signs it, with a secret it shares with the gateway or with a private key whose public half the gateway holds. The plugin jwt-auth on the route checks the signature and the expiry of every token. This guide puts jwt-auth on orders-api and gives mobile-app the credential that checks its tokens. At the end, a request with a valid token that has not expired reaches your backend, and a request without a token, or with one that has no expiry or an expiry that has passed, is answered 401.

Before you start​

  • This guide starts from orders-api as Route requests to a backend leaves it, with no other plugin. A plugin from another guide that this page does not name can change the answers below; remove it first or work on a copy of the route.
  • One authentication plugin per route. With key-auth from Require an API key still on orders-api, every request also needs a key, and a token alone is answered 401. Do this guide on a copy of orders-api, or remove key-auth from it first.
  • To remove key-auth, open the actions menu at the right of the key-auth row on the route's Plugins tab and select Remove. Put it back when you return to the other guides, which start from orders-api with key-auth.
  • The host name of your gateway. In the console it is the first part of the Address line on a route's Overview tab, api.example.com in this page's examples; replace it in every request below.
  • The requests below call /orders/42.
  • From the team that runs the client's sign-in service: the algorithm it signs with and the secret you agree on, or its public key. Each token it issues names the credential in its key claim and carries an exp claim, the time it expires.
  • On a route that already serves clients, do the steps in a different order. Create the consumer and its credential first, steps 4 and 5, and add jwt-auth to the route last, since from then on every request without a valid token is refused.
  • The Editor or Environment administrator role in this environment. Only a role that manages consumers and credentials can create them and see a secret.

Steps​

1. Open the route's Plugins tab​

Open orders-api under Routes, select its Plugins tab, then Add plugin at the top right of the tab.

2. Choose jwt-auth​

Open the Authentication category, or type jwt-auth in the search box. Select jwt-auth, then Configure. A panel with the plugin's form opens.

3. Require an expiry, hide the token and add the plugin​

Fill in these fields and leave the rest as they are.

FieldValueWhy
Claims to verify, under Basicstick expA token without an exp claim is refused, so every token the gateway accepts stops working at some point.
Hide credentials, under BasicsonThe Authorization header that carried the token is removed before the request goes on, so your backend never sees it.

Leave Header, under Behavior, at its default, authorization.

Select Add plugin at the bottom of the panel. The tab now lists jwt-auth, and the plugin is saved like this:

The Add jwt-auth panel with the header the token arrives in, expiry checked and the token hidden from the backend.
jwt-auth · as the console saves it
{
"claims_to_verify": [
"exp"
],
"header": "authorization",
"hide_credentials": true
}

From now on, a request to orders-api whose token is missing or fails the check is answered 401. The next steps create the credential that checks the tokens of mobile-app.

4. Create the consumer​

If mobile-app exists already, go on with step 5. Otherwise create it as step 4 of Require an API key shows, with the username mobile-app.

5. Give the consumer its signing secret​

Create a credential on mobile-app as steps 1 to 3 of Give a client its key, and replace it show, with these values. Leave the other fields as they are.

FieldValueWhy
Credential IDmobile-app-jwtA name for this credential. The ID cannot be changed later.
Pluginjwt-authThe plugin this credential is for.
Keymobile-appThe key claim the token carries. The tokens of mobile-app carry mobile-app in their key claim, and that is how the gateway finds this credential.
AlgorithmHS256The default, signing with a shared secret. Choose the algorithm the sign-in service uses.
Secretthe secretThe secret the sign-in service signs its tokens with. Treat it like a password: long and random, from a password generator, made for this one client.

Select Create credential. The credential is saved like this:

jwt-auth credential · as the console saves it
{
"key": "mobile-app",
"algorithm": "HS256",
"secret": "<secret>",
"lifetime_grace_period": 30
}

The example's Grace period is 30 seconds. You may leave the field at its default, 0. A credential with HS256 and no secret is refused when you save.

6. Hand the secret over​

The sign-in service needs the same secret to sign the tokens. Give the secret to the team that runs the service the way you pass other secrets, never by mail or chat. The tokens are signed outside the gateway, by that service, and the client sends one with every request.

Check it​

Send a request with a token signed for mobile-app that has not expired. Then send one with a token that carries no exp, one whose exp has passed, and one without a token.

Sign your own token with the secret of your credential and the algorithm HS256, and send it in place of the token shown below. Put mobile-app, the credential's Key, in its key claim and a time in the future in its exp claim.

curl
curl -i -L 'https://<gateway host>/orders/42' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJtb2JpbGUtYXBwIiwiZXhwIjo0MTAyNDQ0ODAwfQ.juV0PvxTYr4iEwcCLKjJWVyMyGklH9TXmUkre_57Etc'
A token that has not expired
A request whose Authorization header carries a token mobile-app signed with its secret, not yet expired, is answered 200; the token is removed before the request reaches the backend, which receives X-Consumer-Username: mobile-app instead.
A token without exp
A token signed with the right secret but without an expiry claim is answered 401 with failed to verify jwt.
A token that has expired
A token whose expiry has passed is answered 401 with failed to verify jwt.
Without a token
A request without a token is answered 401 with Missing JWT token in request.

Sign with a key pair instead​

A partner that signs its tokens with its own private key gives you only the public key, as partner-portal does. The private key never leaves the partner. Create the consumer partner-portal as step 4 of Require an API key shows, if it does not exist yet. Then give it a credential as in step 5, with these values.

FieldValue
Credential IDpartner-portal-jwt
Pluginjwt-auth
Keypartner-portal
AlgorithmRS256, or the algorithm the partner signs with. The Public key field then replaces Secret.
Public keyThe public key, as PEM text.
jwt-auth credential · as the console saves it
{
"key": "partner-portal",
"algorithm": "RS256",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0q8KUqx3QiPmNjsS4fv3\nDUNTYCE1RU18lC4v8VG0YZxSTX2qVk2aEUh/Pe5EvLsGRhNEujmIGdw2RbaJjJVf\n9EVPYCrTc8PZnrWP0P1XHHkcMdOdln6Ph1IUOcdNeLemVSnJH7i6y3MKhrxfDYDp\nFFC0USTd5UBvmVkZZ1bjx4g397jYb730jbtrFLNK1rmb+sNq0ttqtpltWHxN81Vh\n05AC6Pn0u8wo5Q4Y6RKZTAyA+envySlvXC2pjQUK1d4nh1FRxbPvRBMB1cu3VJIi\npsq8yiiGKRIb9sZ/80pWCBY8PWsVfGlMl+bbuTxZ/kq6UG63+0trpQl2EUXBNnEk\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
}

A credential with RS256 and no public key is refused when you save. The answers below hold on a route whose jwt-auth leaves Claims to verify empty. On orders-api, with exp ticked, the tokens of partner-portal need an exp claim as well.

A token signed with the private key
A request whose token is signed with the private key that matches the public key, and names the key, is answered 200, and the backend receives the consumer's name in X-Consumer-Username.
Without a token
A request without a token is answered 401 with Missing JWT token in request.

What to know​

  • Your backend receives the consumer's username in X-Consumer-Username, the name of the consumer whose credential checked the token.
  • A ticked claim must be in every token. A token that carries exp or nbf has it checked even when it is not ticked.
  • Only exp and nbf can be ticked; iat is refused when you save.
  • One plugin per credential. A consumer that also signs in with an API key holds a key-auth credential beside this one.
  • Every consumer whose credential checks the token passes. To let only some of them in, add Allow only named clients on the same route.
G8 dev