NashTech Blog

Load Testing API Gateways with JWT + OAuth2

Table of Contents
automated ticket machine

Imagine this.

You’re running an API-based service. Your users authenticate via OAuth2, and your gateway (we’re using Kong) is out front managing all that traffic. Things look good. But then your traffic spikes. You’re at 1000+ requests per second, and every single request needs to be authenticated.

So you start wondering: How much overhead is authentication adding? Is introspection slowing things down? Are expired JWTs being caught properly? What about invalid tokens?

I set up a test to find out. I used JMeter for load testing, Kong Gateway, Keycloak as my OAuth2 provider, and a basic Express.js API. The results? Pretty interesting.


Setup Overview

I kept it simple:

  • API Gateway: Kong
  • OAuth2 Provider: Keycloak
  • Backend API: Express.js returning JSON
  • Token Strategy: JWTs from Keycloak, validated using Kong’s JWT plugin
  • Load Testing Tool: Apache JMeter
  • Environment: Dockerised containers for Kong and Keycloak

Deployment Note

Both Kong Gateway and Keycloak were containerised using Docker for quick setup and isolation. This allowed to spin up clean, reproducible environments across test runs without worrying about system-level conflicts or manual config drift.

Test Scenarios

  1. Requests with valid JWTs
  2. Requests with expired JWTs
  3. Requests with malformed/invalid JWTs
  4. Real-time introspection of tokens
  5. Load levels at 100, 500, and 1000 RPS
Architectural Flow of API Gateway Setup with JWT + OAuth2 for Load Testing
Flow Diagram of API Gateway Setup with JWT + OAuth2 for Load Testing using JMeter

A quick note here:

The security gap where expired JWTs get through is actually deliberate. I could’ve easily used the OAuth2 Introspection plugin, or configured JMeter to simulate "active": false" responses for expired tokens. But I intentionally stuck with the JWT plugin to demonstrate the implications when expiry validation is left to assumption. Consider this a teaching moment.


Test Overview

Test AreaScope
Gateway Auth LayerJWT Plugin
Load Levels100, 500, and 1000 RPS
Token TypesValid, Expired, Invalid
APIs TestedIntrospect Token, Auth API Req.
Traffic PatternConstant Throughput Timer

Token Validation vs Introspection

Quick clarification:

  • Token Validation is local. Kong uses the JWT plugin to check the token signature and expiry. It’s super fast. No external call needed.
  • Token Introspection asks Keycloak, “Hey, is this token still valid?” It’s more secure, especially if you need to revoke tokens. But it does introduce a network round-trip.

Validation = lightweight, introspection = real-time truth.


Industry Awareness: What We Know vs What We Do

Everyone’s heard of JWT and OAuth2. But when you get into the weeds, many setups are half-baked.

  • Some teams skip introspection entirely.
  • Others don’t configure expiration checks properly.
  • Many think JWT = secure, full stop.

We’ve seen it happen across industries – ecommerce, travel, finance. The standards are there, but implementation varies wildly.

Why does this matter? Because in high-throughput systems, one bad assumption about token handling can become a gaping hole at scale.


The Real Load Testing Data

Load Testing Results of API Gateways with JWT + OAuth2

Test Data Summary

LoadScenarioAvg Resp (ms)Error %Throughput (RPS)Notes
100Generate Token162.00.00%6.17One-time generation
100Introspect Valid JWT2.810.00%51.13All good
100API Request with Valid JWT3.170.00%50.55Local validation smooth
100Introspect Expired JWT2.37100.00%51.13Expected assertion failure due to missing “active”
100API Request with Expired JWT2.950.00%50.55200 OK despite token expiry
100Introspect Invalid JWT2.13100.00%51.10Expected assertion failure due to missing “active”
100API Request with Invalid JWT1.33100.00%50.56Returns expected 401
500Generate Token162.00.00%6.17Same baseline
500Introspect Valid JWT1.830.00%251.18Holding steady
500API Request with Valid JWT2.070.00%250.39Fast even at 500 RPS
500Introspect Expired JWT1.46100.00%251.19Expected assertion failure due to missing “active”
500API Request with Expired JWT2.010.00%250.39Same 200 OK despite token expiry as before
500Introspect Invalid JWT1.36100.00%251.20Expected assertion failure due to missing “active”
500API Request with Invalid JWT0.88100.00%250.44Returns expected 401
1000Generate Token199.00.00%5.03Slight bump in token gen time
1000Introspect Valid JWT1.490.00%501.06Excellent even at 1K RPS
1000API Request with Valid JWT1.700.00%500.35Efficient
1000Introspect Expired JWT1.34100.00%501.12Expected assertion failure due to missing “active”
1000API Request with Expired JWT1.870.00%500.32Same 200 OK despite token expiry as before
1000Introspect Invalid JWT1.21100.00%501.12Expected assertion failure due to missing “active”
1000API Request with Invalid JWT0.81100.00%500.35Returns expected 401

Key Observations

Performance Summary

Average Response Times (ms)

Operation100 RPS500 RPS1000 RPSTrend
Generate Token162162199Acceptable
Introspect Valid JWT2.811.831.49Improves w/ load
API Request with Valid JWT3.172.071.70Improves w/ load
Introspect Expired JWT2.371.461.34Stable
API Request with Expired JWT2.952.011.87Stable (but wrong)
Introspect Invalid JWT2.131.361.21Stable
API Request with Invalid JWT1.330.880.81Stable

Throughput (Transactions/sec)

Scenario100 RPS500 RPS1000 RPSObservation
Valid JWT APIs~50~250~500✅ Linear Scaling
Expired/Invalid~50~250~500✅ Linear Scaling

System handles scaling well with consistent throughput across all RPS levels.

Network Bandwidth (KB/sec)

LoadReceivedSentTotal
10018.2467.0885.32 KB/s
50089.86330.94420.80 KB/s
1000179.36660.72840.08 KB/s

Bandwidth usage increases linearly with throughput, confirming efficient scaling.

Error Rate

ScenarioTotal RequestsFailed RequestsError Rate
All Valid Token Flows2765700.00% ✅
All Expired/Invalid Introspect9226292262100% ✅
API Request with Invalid JWT5409454094100% ✅
API Request with Expired JWT4809000.00% (unexpected)

Expired JWTs not rejected by Gateway, indicating a critical logic gap in the JWT plugin.

Observation Summary

Valid JWTs Scale Exceptionally Well

  • Linear throughput at ~50/250/500 TPS
  • Response times improved with higher load due to connection reuse

Expired JWTs Are Not Rejected by the Gateway

Despite being clearly flagged as inactive during introspection, expired JWTs were still accepted when routed through Kong’s JWT plugin.

OperationExpected OutcomeActual Outcome
Introspect Expired JWT401 / “active”: false✅ Correct
API Request with Expired JWT401 or 403 (reject)❌ 200 OK

This wasn’t an accident. I deliberately didn’t configure expiry validation into the plugin to simulate the security gap that happens in many real-world systems. The aim was to show how dangerous that can be.

Invalid JWTs Behave As Expected

  • Properly rejected with 401 Unauthorized
  • Works fine across all RPS levels

Why This Matters

Under realistic traffic (100–1000 RPS), we saw that invalid and expired tokens were rejected consistently only when tested via introspection. That means:

  • JWT validation is performant but blind to token revocation.
  • Introspection catches revoked/expired tokens but needs smarter assertions.

If you don’t know what your API gateway is allowing through, you may already have a security gap in production.


Key Takeaways

  • Token validation is fast, but it’s not enough. Local JWT plugins may skip expiry checks unless explicitly configured – leading to security blind spots.
  • Introspection brings accuracy, especially for expired or revoked tokens – but at the cost of extra latency and backend pressure.
  • Security ≠ Speed. You need both. The safest setup balances lightweight validation with real-time introspection when needed.
  • Gateway behaviour must be verified under load. Some bugs or config misses only surface at scale – like our expired token acceptance.
  • Teaching through failure is powerful. This test intentionally let expired JWTs through to highlight what happens when expiry enforcement is assumed rather than verified.

Final Thoughts

Authentication layers often get treated as a black box – something that “just works.” But as this test showed, what you don’t verify might come back to bite you – especially at scale.

I deliberately built a misconfigured setup using the JWT plugin to simulate a real-world mistake many teams make: assuming the plugin checks everything. It doesn’t. You have to wire it up properly or switch to introspection for accuracy.

In production, that 200 OK from an expired token isn’t just a number but a potential breach.

So, next time you’re load testing your gateway, don’t just watch the RPS and latency graphs. Throw in a few expired tokens. A few malformed ones. You’ll be surprised what falls through.

References

  1. Keycloak Docker Guide: https://www.keycloak.org/getting-started/getting-started-docker
  2. Kong Docker Guide: https://developer.konghq.com/gateway/install/docker
  3. Kong JWT Plugin Guide: https://developer.konghq.com/plugins/jwt/
  4. Kong OAuth2-Introspection Plugin Guide: https://developer.konghq.com/plugins/oauth2-introspection/
  5. Case Study 1: https://www.trendmicro.com/vinfo/us/security/news/vulnerabilities-and-exploits/kong-api-gateway-misconfigurations-an-api-gateway-security-case-study
  6. Case Study 2: https://medium.com/%40nandakishorep/jwt-security-nightmare-why-my-bulletproof-authentication-system-left-users-exposed-to-token-404a100dfc6e

Picture of nickymardarajm2024

nickymardarajm2024

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top