Kong Enterprise 3.3 delivers enhanced security, usability, and platform reliability. Learn more
Engineering

How JWT Authentication Works for Microservices: API Gateway Tutorial

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.

API 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 JSON Web Token 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 by clicking here.

What Is a 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.

Join us this September for Kong Summit 2022

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 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.

JWT vs. OAuth 2.0

JWT is a different kind of authentication from OAuth 2.0, where the tokens are often long, random strings without encoded payloads. For OAuth 2.0, an identity provider keeps track of the OAuth 2.0 tokens it hands out on the server. The provider who issued the JWT token is the entity that validates it. For this article, I’ll focus on JWT.

Blog Post: 4 Steps to Authorizing Services With the Kong Gateway OAuth2 Plugin

JWT vs. OpenID

A separate but related authentication protocol is OpenID, which allows for authentication at one service to be performed by a third-party identity provider service. Kong’s OpenID Connect plugin would help facilitate this kind of interaction.

Blog Post:Getting Started With Kong’s OpenID Connect Plugin

 

Basic JWT and API Gateway 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 to JWT authentication 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.

Lastly, I’ll cover advanced use cases for the plugin. Ready to dive in to our JWT Authentication Tutorial? Here we go!

 

Want to set up key authentication for your API gateway with clicks instead of code? Try Konnect for free >>

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:

~$ 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.

hello world browser

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. Here’s the response:

Insomnia Get Request

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.

~/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.

/* 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:

Hello World Local Host 8000

Similarly, in Insomnia:

Insomnia Hello World Local Host 8000

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:

~/project$ sudo kong restart

When you try the request in Insomnia again, here is the response you should get:

Insomnia Local Host 8000 Unauthorized

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:

/* 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:

  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:

~/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:

Insomnia 8001 JWT Credentials for Kong Gateway

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.

JWT Good

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.

JWT Good Encoded

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.

Insomnia Set JWT Bearer Authentication for API Gateway

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

Insomnia JWT for Kong Gateway

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).

Kong Gateway JWT Upstream Headers

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.”

Insomnia JWT for Kong Gateway Bad Secret

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.

Insomnia JWT for Kong Gateway Bad Kid

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

Insomnia JWT for Kong Gateway Expired

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.

From here, you should have the foundation needed to deal with more complex cases. Let’s look at a few below.

Other Use Cases for Kong Gateway JWT Plugin

Every User Is a Consumer

Earlier, I illustrated a straightforward case where a login server acts as your service’s sole consumer. Though that’s a standard model, you can also configure Kong to hook into your user database. You can tell Kong Gateway that every user in your database is a consumer, and they each have a unique JWT secret. Your login server could generate JWTs, but a user’s successful login would result in a JWT signed with a secret that is specific to that individual user. This type of granular JWT issuing could allow for more precise tracking and access logging through Kong.

With this model, you could even let users (consumers) know what their kid and signing secret are, and your users could generate their own JWT to use whenever they want.

Authentication for Some Routes, but Not Others

In this project, I enabled the JWT API gateway plugin on the api-requests routes. You can imagine a use case where Kong is configured with a single service (like an API server), but multiple routes are configured to hit different subsets of paths. For example, you might have a route for endpoints related to product inventory but another route for endpoints related to order history. Perhaps you could enable the JWT plugin on the order history route—which requires authentication—but not on the publicly accessible inventory counts route. With Kong, you can pick and choose which services or routes should get the JWT plugin and which shouldn’t.

“Anonymous” Access

The plugin also has an anonymous consumer option. With this option, Kong allows the user to anonymously access the upstream service if authentication fails. You can imagine an upstream service like a web application, where “anonymous” (not logged-in) users can still access parts of your application with limited privileges. 

Public/Private Keys

The common (and simpler) usage for generating a JWT is to sign it with a secure secret kept by the JWT issuer. You can configure the Kong plugin for generating a JWT with public/private keys. In this model, the consumer maintains a pair of keys — one public and one private. The private API key is secure, while the public key is available to hand out. When configuring a consumer’s JWT credentials for Kong, the system stores the public key with Kong instead of a secret. When generating a JWT, the consumer signs it with his or her private key. The JWT API gateway plugin uses the public key for this consumer to authenticate that the consumer signed the JWT.

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 API Gateway and the JWT plugin. It’s time to get to work.

Want more tutorials?

Have questions or want to stay in touch with the Kong community? Join us wherever you hang out:

⭐ Star us on GitHub

🐦 Follow us on Twitter

🌎 Join the Kong Community

🍻 Join our Meetups

❓ ️Ask and answer questions on Kong Nation

💯 Apply to become a Kong Champion

 

Share Post

Subscribe to Our Newsletter!