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:
- Set up a basic Node.js Express server with a single endpoint.
- Set up Kong Gateway as an API gateway to your server.
- 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.
Next, add Express to your project:
~/project$ yarn add express
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}`)
})
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
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
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
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
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
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
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
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
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"
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:
- 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 - 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
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)