DEV Community

Cover image for Authentication with Kong's JWT Plugin
Julia Salem for Kong

Posted on • Originally published at konghq.com

Authentication with Kong's JWT Plugin

As you build and maintain more applications, your authentication strategy becomes increasingly important. It may also be top of mind for your boss since technology leaders cited “improve application security” as one of their top priorities in this year’s Digital Innovation Benchmark.

The Kong Gateway JWT plugin is one strategy for API gateway authentication. JWT simplifies authentication setup, allowing you to focus more on coding and less on security.

Authentication Is Tough

You know you need a secure front door to your system. If requests don’t have the right credentials, the door should remain locked. If they do have the proper credentials, the entry should be smooth.

But how do you verify that credentials are authentic? And how do you make sure there aren’t other ways for those without the right credentials to get into your system?

Let’s walk through those scenarios as I demonstrate how to secure a service (in this case, an API server) with Kong Gateway and its JWT plugin. I’ll cover all the steps to set up, configure and test the service — giving you the foundational knowledge needed to implement these tools independently.

Core Concepts

First, let’s cover the core technologies. If you’re already familiar with these and just want to get started, feel free to skip ahead.

What Is Kong Gateway?

As more companies move from monolithic systems to microservices, a decoupled front-line API gateway to those services — providing authentication, traffic control, request and response transformation — becomes increasingly crucial. Kong Gateway, which is open source, serves as that thin layer between your users and your upstream microservices.

What Is JWT?

The JSON Web Token (JWT) format lets two parties exchange secure claims. It’s a way of saying, “I am so-and-so, which should give me access to that resource. Here is my access token to prove it.”

A JWT has a data payload signed by a trusted party to prevent spoofing. An authorizer verifies that the JWT token is authentic, allowing (or forbidding) access to that resource. Typically, a JWT payload is not encrypted; it’s open for the whole world to read. However, what’s critical is the authenticity of a token, which depends on a trusted party signing it.

What Does Kong's JWT API Gateway Plugin Do?

In this approach, the plugin serves as the JWT authorizer. It authenticates the JWT in the HTTP request by verifying that token’s claims and ensuring a trusted party signed it. Then, depending on whether these steps were successful, Kong Gateway routes the upstream service request.

Keep in mind that authentication in this context means validating the user’s credentials. That’s the job of the JWT plugin. There’s no way to know how a user got a valid JWT. The system just knows that the user has one and is presenting it for authentication. If the JWT is authentic, you can be confident that the user is who they say.

The Basic Use Case

In this basic use case, I have a login server that accepts login attempts with a user’s email and password. If the email/password checks out, the server generates and signs a JWT and hands it back to the user.

With JWT in hand, the user tries to access our microservice: a simple API server with a single endpoint. Kong Gateway sits in front of your API server, using the JWT plugin for authentication. The user presents his JWT with his request.

First, the plugin verifies the token’s authenticity. Next, it confirms the installation steps of the claims inside the payload. A common claim used is an expiration timestamp for the access token. It’s essentially saying, “This token is valid until this date and time.” So, the plugin will check the token’s expiration date.

If the JWT passes all the necessary checks, Kong Gateway grants access to the requested server endpoint. Otherwise, it responds with 401 Unauthorized.

The approach is quite simple:

  1. Set up a basic Node.js Express server with a single endpoint.
  2. Set up Kong Gateway as an API gateway to your server.
  3. Enable the JWT plugin to protect your server endpoint with JWT authentication.

1. Set Up a Node.js Express Server and Endpoint

On your local machine, create a folder for your project. Then, initialize a new Node.js project. In the following examples, I’ll use yarn, but you could use npm too:


3
~$ mkdir project
~$ cd project
~/project$ yarn init  # Use all of the yarn defaults here.
Enter fullscreen mode Exit fullscreen mode

Next, add Express to your project:

~/project$ yarn add express
Enter fullscreen mode Exit fullscreen mode

In your project folder, create the entry point file, index.js, which will spin up an Express server with a single endpoint. Allow a GET request to /, which will respond with the string, “Hello world!”

/* PATH: ~/project/index.js
*/

const express = require('express')
const server = express()
const port = 3000

server.get('/', (req, res) => {
  console.log(req.headers)
  res.status(200).send('Hello world!')
})

server.listen(port, () => {
  console.log(`Server is listening on http://localhost:${port}`)
})
Enter fullscreen mode Exit fullscreen mode

That was simple enough! Your single endpoint should log the request headers and then send “Hello world!” back to the client with a 200 status.

Start your server:

~/project$ node index.js
Enter fullscreen mode Exit fullscreen mode

You can use your browser to test this new endpoint by visiting http://localhost:3000.

Your API server endpoint should be working now!

Next, use Insomnia to send the request and inspect the response. Because of its usability, you’re going to want to use Insomnia exclusively once you start sending requests with a JWT.

In Insomnia, create a GET request to http://localhost:3000.

In Insomnia, you should get a 200 OK with “Hello world!” in the response body.

It looks like the API server is up and running. Now, it’s time to put Kong Gateway in front of it.

2. Set Up Kong Gateway

I won’t cover the details here, but the Kong Gateway installation steps may look different depending on your system.

Once you’ve installed Kong, you’ll need to take a few additional steps.

DB-Less Declarative Configuration
There are two primary ways to configure Kong. Imperative configuration issues step-by-step configuration commands to Kong through its admin API. Meanwhile, declarative configuration stores the entire configuration in a single .yml file then loads it into Kong upon startup. Additionally, you can configure Kong to hook into your database, providing more control over the different nodes it manages.

For the simple setup example, I’ll use database-less declarative configuration. When you start up Kong, you’ll tell it where to find a .yml file with all of the configuration declared within.

In your project folder, run the following command, which generates an initial kong.yml declarative configuration file.


2
3
4
5
6
7
8
9
~/project$ kong config init
~/project$ tree -L 1
.
├── index.js
├── kong.yml
├── node_modules
├── package.json
└── yarn.lock
1 directory, 4 files
Enter fullscreen mode Exit fullscreen mode

Next, you’ll need to configure the system’s kong.conf file before starting up Kong. If you’re working on Ubuntu, you’ll be working in /etc/kong. Here is a template to copy over and then edit.

~/project$ cd /etc/kong
/etc/kong$ sudo su

root:/etc/kong$ tree
.
├── kong.conf.default
└── kong.logrotate
0 directories, 2 files

root:/etc/kong$ cp kong.conf.default kong.conf 
Enter fullscreen mode Exit fullscreen mode

There are only two edits you need to make in your kong.conf file.

# PATH: /etc/kong/kong.conf

# Around line 839, uncomment and set to off
database = off

# Around line 1023, uncomment and set an absolute path to kong.yml
declarative_config = /PATH/TO/YOUR/project/kong.yml
Enter fullscreen mode Exit fullscreen mode

When Kong starts up, it will be in DB-less mode, meaning it will look to your project’s kong.yml file for a configuration.

Finally, you’ll need to edit your kong.yml file to set up a gateway in front of your API server “hello world” endpoint.


2
3
4
5
6
7
8
9
10
11
12
13
/* PATH: ~/project/kong.yml
*/

_format_version: "2.1"

services:
- name: my-api-server
  url: http://localhost:3000/
routes:
- name: api-requests
  service: my-api-server
  paths:
    - /api
Enter fullscreen mode Exit fullscreen mode

Let’s go over this.

The _format_version metadata specifies the version number of your declarative configuration format.

Next, you define your service, which Kong describes as “an entity representing an external upstream API or microservice.” You can name your service my-api-service and specify its URL — you’ll recall that the Express server listens for requests at http://localhost:3000.

Next, define routes, which “determine how (and if) requests are sent to their Services after they reach Kong Gateway.” The (local) URL for Kong is http://localhost:8000. You should declare your route so that Kong listens for requests at http://localhost:8000/api, then routes to your service.

Let’s see this in action. Make sure your Express server is running in a separate terminal. Then, start Kong.

~/project$ sudo kong start
Enter fullscreen mode Exit fullscreen mode

In your browser, go to http://localhost:8000/api

Kong Gateway is up. Finally, add authentication.

3. Attach JWT Plugin to Kong Gateway

To add the JWT plugin, add a “plugins” definition to your kong.yml file:

/* PATH: ~/project/kong.yml
*/

_format_version: "2.1"

services:
- name: my-api-server
  url: http://localhost:3000/
routes:
- name: api-requests
  service: my-api-server
  paths:
    - /api
plugins:
- name: jwt
  service: my-api-server
  enabled: true
  config:
    key_claim_name: kid
    claims_to_verify:
    - exp
Enter fullscreen mode Exit fullscreen mode

Here, you can add the plugin named jwt and attach it to your service called my-api-server. For its configuration options, tell the plugin to check the exp value to verify that the access token has not expired.

At this point, restart Kong and see what happens:

1
~/project$ sudo kong restart
Enter fullscreen mode Exit fullscreen mode

The response is 401 Unauthorized. Excellent! Kong now requires a valid JWT for any requests to your API server. Next, you need to tell Kong what constitutes a valid JWT.

In kong.yml, you need to add a consumer and a credential. Kong describes consumers as being “associated with individuals using your Service, and can be used for tracking, access management, and more.” In a more elaborate setting, every one of your API users could be a consumer. That’s a use case you can read more about towards the end of this article. In this situation, your login server is your consumer. Your login server will be the entity generating JWTs and handing them out. Users who make a request to Kong will be holding a “login server” JWT.

Edit your kong.yml file by adding the “consumers” and “jwt_secrets” definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* PATH: ~/project/kong.yml
*/

_format_version: "2.1"

services:
- name: my-api-server
  url: http://localhost:3000/
routes:
- name: api-requests
  service: my-api-server
  paths:
    - /api
plugins:
- name: jwt
  service: my-api-server
  enabled: true
  config:
    key_claim_name: kid
    claims_to_verify:
    - exp
consumers:
  - username: login_server_issuer
jwt_secrets:
  - consumer: login_server_issuer
    secret: "secret-hash-brown-bear-market-rate-limit"
Enter fullscreen mode Exit fullscreen mode

You’ve added a new consumer, named login_server_issuer. Then, you added a JWT API gateway credential for that consumer, which contains the secret used to sign JWTs for this consumer. Authentication requires two parts:

  1. The kid (key identifier) value in the JWT header, which is a unique identifier that lets the plugin determine which consumer allegedly issued this JWT
  2. Verification of the consumer’s secret – Was this the secret used to sign this JWT API gateway? If so, then this JWT is authentic.

Before continuing, remember to restart Kong:

1
~/project$ sudo kong restart
Enter fullscreen mode Exit fullscreen mode

If you want to generate a JWT for testing, you need the secret (which you have) and the key to use for the kid value. Kong gives us access to that value through its admin API at http://localhost:8001. You send a GET request to the admin API’s endpoint /consumers/CONSUMER-USERNAME/jwt. This gives us information about this consumer’s JWT credential.

As you inspect this credential’s information, you should see the JWT secret and signing algorithm. What you’re looking for, though, is the key. In the above example, that’s 1nzcMG9Xg7n1lLgmltHnkAmkt7yp4fjZ. This is what you use as the kid value in the JWT header. The Kong plugin will see this kid value, track down the associated consumer and secret, then make sure the JWT was signed with that secret.

To test this, let’s start with the happy path. You need a JWT with a header that includes the correct kid value, signed with the right secret. For simplicity, let’s do this at jwt.io. Here, you can craft your payload, set the signing secret and then copy/paste the resulting JWT.

In the payload, the kid must match the key value from above. Also, because you configured the plugin to check JWT token expiration, you should set the exp (Unix timestamp) far into the future. The name and email are inconsequential; they just demonstrate that you can put other helpful data in the JWT payload.

Lastly, include your JWT secret at the bottom for proper signing. The result is an encoded JWT.

Back in Insomnia, you have your original request that resulted in 401. You need to add “Authorization” to that request. Choose “Auth – Bearer Token,” then paste in your encoded JWT from above.

Now, with a valid JWT attached, resend the request.

Your JWT should have been validated, and Kong routed us to the API server!

If you look back at the terminal running the Express server, you’ll recall that you’re logging the request headers to the console. When the JWT plugin authenticates an access token, it writes some additional values to the upstream headers, namely the consumer id, username and credential identifier (the key value).

But what happens if your JWT is not valid? Let’s test and see.

First, sign the JWT with a different secret. Back at jwt.io, keep the payload, but change the signing secret. Copy the resulting JWT to Insomnia, and send your request again. You’ll get a 401 with “Invalid Signature.”

If your secret is correct, but the kid is incorrect, Kong won’t find an associated credential. Without that credential, there’s no way to find the secret for authenticating the JWT.

Lastly, if the exp value is in the past, then your JWT has expired. Just as you expected, you get the following response.

And that’s it! You should be up and running with Kong Gateway and the JWT Plugin acting as an authentication layer in front of an API server.

Set It and Forget It

Implementing authentication —and getting it right —is hard work. As microservices become the norm, delegating authentication makes more and more sense. “Rolling your own” implementation for JWT authentication can muddy a code base and still leave you wondering if you got it right. By using well-tested and community-adopted services that handle concerns like routing, logging or authentication, developers can shift their focus back to their projects’ unique needs.

With that, you now have a solid foundation for getting started with Kong Gateway and the JWT plugin. It’s time to get to work.

Thanks for walking through this tutorial with us. It was originally published on our blog: https://bit.ly/3eJGhCS

Top comments (0)