VOOZH about

URL: https://dzone.com/articles/secure-your-api-with-jwt-kong-openid-connect

⇱ Secure Your API With JWT: Kong OpenID Connect


Related

  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Secure Your API With JWT: Kong OpenID Connect

Secure Your API With JWT: Kong OpenID Connect

In this article, walk through the issues of session-based authorization and the benefits of stateless tokens, namely JWT.

By Mar. 19, 24 · Tutorial
Likes
Comment
Save
12.5K Views

Join the DZone community and get the full member experience.

Join For Free

Good Old History: Sessions

Back in the old days, we used to secure web applications with sessions. The concept was straightforward: upon user authentication, the application would issue a session identifier, which the user would subsequently present in each subsequent call. On the backend side, the common approach was to have application memory storage to handle user authorization - simple mapping between session ID and user privileges. 

Unfortunately, the simple solution had scaling limitations. If we needed to scale an application server, we used to apply session stickiness on the exposed load balancer:

 Or, move session data to shared storage like a database. 

That caused other challenges to tackle: how to evenly distribute traffic for long living sessions and how to reduce request processing time for communication with shared session storage.

Distributed Nature of Authorization

The stateful nature of sessions becomes even more troublesome when we consider distributed applications. Handling proper session stickiness, and connection draining on a scale like multiple microservices gives no easy manageable solution.

Stateless Authorization: JWT

Luckily, we can use a stateless solution - JWT - which is based on a compact and self-contained, encoded JSON object acting as a replacement for session ID for client/server communication. The idea is to encode user privileges or roles into a token and sign data with a trusted issuer to prove token integrity. In this scenario, the user, once authenticated, gets an access token in response with all data required for authorization - no more server session storage needed. The server during authorization needs to decode the token and get user privileges from the token itself. 

Exposing Unprotected API in Kong

To see how things can work, let’s use Kong, acting as an API Gateway for calling upstream service. For this demo, we will use the Kong Enterprise edition together with the OpenID Connect plugin handling JWT validation. But let's first expose some REST resources with Kong. 

To make the demo simple, we can expose a single/mock endpoint in Kong which will proxy requests to the httpbin.org service. Endpoint deployment can be done with a declarative approach: we define the configuration for setting up the Kong service that will call upstream. Then the decK tool will create respective resources in Kong Gateway. The configuration file is as follows: 

YAML
_format_version: "3.0"
_transform: true

services:
 - host: httpbin.org
 name: example_service
 routes:
 - name: example_route
 paths:
          - /mock


Once deployed, we can verify endpoint details in Kong Manager UI:

For now, the endpoint is not protected and we can call it without any authorization details. Kong Gateway is exposed on a local machine on port 8000, so we can call it like this:

➜ ~ curl http://localhost:8000/mock/anything

{

 "args": {}, 

 "data": "", 

 "files": {}, 

 "form": {}, 

 "headers": {

 "Accept": "*/*", 

 "Host": "httpbin.org", 

 "User-Agent": "curl/8.4.0", 

 "X-Amzn-Trace-Id": "Root=1-65e2f62e-2ea7165246c573e24a3efeaf", 

 "X-Forwarded-Host": "localhost", 

 "X-Forwarded-Path": "/mock/anything", 

 "X-Forwarded-Prefix": "/mock", 

 "X-Kong-Request-Id": "3cef792ded0dfb53575cd866c20aba42"

 }, 

 "json": null, 

 "method": "GET", 

 "url": "http://localhost/anything"

}


Securing API With OpenID Connect Plugin

To secure our API we need two things:

  • IdP server, which will issue JWT tokens
  • Kong endpoint configuration that will validate JWT tokens

Setting up an IdP server is out of scope for this blog post, but for the demo, we can use Keycloak. In my test setup, I created a “test” user which is granted to have a “custom-api-get” scope - we will use this scope name later on for authorization with Kong. To get a JWT token, we need to call the Keycloak token endpoint. It returns an encoded token, which we can decode on the jwt.io website:

On the Kong side, we will define endpoint authorization with the OpenID Connect plugin. For this, again, we will use the decK tool to update the endpoint definition.

YAML
_format_version: "3.0"

_transform: true


services:

 - host: httpbin.org

 name: example_service

 routes:

 - name: example_route

 paths:

 - /mock

 plugins:

 - name: openid-connect

 enabled: true

 config:

 display_errors: true

 scopes_claim:

 - scope

 bearer_token_param_type:

 - header

 issuer: http://keycloak:8080/auth/realms/master/.well-known/openid-configuration

 scopes_required:

 - custom-api-get

 auth_methods:

                - bearer

In the setup above, we stated that the user is allowed to call the endpoint if the JWT token contains the “custom-api-get” scope. We also specified how we want to pass the token (header value). To enable JWT signature verification, we also had to define the issuer. Kong will use this endpoint internally to get a list of public keys that can be used to check token integrity/signature (the content of that response is cached in Kong to avoid future requests). 

With this configuration, calling an endpoint without a token is not allowed. The plugin returns error details as follows:

➜ ~ curl http://localhost:8000/mock/anything

{"message":"Unauthorized (no suitable authorization credentials were provided)"}


To make it work, we need to pass a JWT token (for the sake of space, the token value is not presented):

➜ ~ curl http://localhost:8000/mock/anything --header "Authorization: Bearer $TOKEN"

{

 "args": {}, 

 "data": "", 

 "files": {}, 

 "form": {}, 

 "headers": {

 "Accept": "*/*", 

 "Authorization": "Bearer $TOKEN", 

 "Host": "httpbin.org", 

 "User-Agent": "curl/8.4.0", 

 "X-Amzn-Trace-Id": "Root=1-65e30053-4f1b17b771c240463a878c41", 

 "X-Forwarded-Host": "localhost", 

 "X-Forwarded-Path": "/mock/anything", 

 "X-Forwarded-Prefix": "/mock", 

 "X-Kong-Request-Id": "c1cf555ab43d951f73f72a30d5546516"

 }, 

 "json": null, 

 "method": "GET", 

 "url": "http://localhost/anything"

}


We should remember that tokens have a limited lifetime (in our demo, it was 1 minute), and the plugin verifies it as well. Calling the endpoint with an expired token returns the error:

curl http://localhost:8000/mock/anything --header "Authorization: Bearer $TOKEN"

{"message":"Unauthorized (invalid exp claim (1709375597) was specified for access token)"}


Summary

In this short post, we walked through the issues of session-based authorization and the benefits of stateless tokens, namely JWT. In a microservices solution, we can move authorization from microservice implementation into a centralized layer like Gateway. We just scratched the surface of JWT-based authorization, but we can implement more advanced scenarios by validating additional claims. If you’re interested in JWT details, I recommend you familiarize yourself with the specifications. Practice will make you an expert! 

API OpenID authentication JWT (JSON Web Token) security

Opinions expressed by DZone contributors are their own.

Related

  • Securing REST APIs With Nest.js: A Step-by-Step Guide
  • Navigating the API Seas: A Product Manager's Guide to Authentication
  • What D'Hack Is DPoP?
  • Using OKTA as Client Provider in Mulesoft

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

Let's be friends: