Create your own
Lesson illustration

API Gateway JWT Validation

Hello! Welcome back to our journey into building production-ready microservices.

In our last lesson, we successfully secured an individual microservice by configuring it as an OAuth 2.0 Resource Server. This is a common and robust pattern, but it requires each service to independently handle token validation. Today, we'll explore a different architectural approach that is central to many large-scale systems.

Our learning outcome is to implement JWT token validation at the API Gateway for centralized authentication. By handling security at the edge of our system, we can simplify our downstream services and enforce security policies consistently. This is a critical pattern and a frequent topic in senior-level system design interviews.

Why Centralize Authentication at the Gateway?

Before we dive into the implementation, let's discuss the "why". In the previous model, every service was its own bouncer, checking IDs at the door. In the centralized model, the API Gateway acts as a single, highly-secure entrance for the entire complex.

Microservices Architecture with API Gateway and JWT Authentication
This diagram illustrates the architecture we're aiming for. All client requests pass through the Spring Cloud Gateway, which validates the JWT before routing the request to the appropriate backend service. The backend services can now trust that any request they receive has already been authenticated.

The key benefits of this approach are:

  • Simplified Microservices: Your business services (e.g., product-service, order-service) no longer need to contain security validation logic. They can focus purely on their business domain.
  • Consistent Security Policy: All security rules are enforced in one place, reducing the risk of inconsistent or misconfigured security in one of the many downstream services.
  • Reduced Network Latency: Downstream services don't need to make extra calls to an authorization server's /userinfo or /introspect endpoints. The JWT is validated once at the edge.
  • Single Point of Attack: While this sounds like a negative, it allows you to focus your most robust security measures (like rate limiting, WAF, advanced monitoring) on a single component: the gateway.

The Mechanism: Spring Cloud Gateway Filters

Spring Cloud Gateway is built on a foundation of filters. A GatewayFilter allows you to intercept and modify requests before they are routed to a downstream service, and to modify the response on its way back to the client.

For our use case, we will create a custom authentication filter with the following logic:

  1. Intercept every incoming request.
  2. Check if the request is for a public endpoint (e.g., /auth/token). If so, let it pass through.
  3. For protected endpoints, extract the Authorization header.
  4. Validate the JWT found in the header.
  5. If the token is valid, allow the request to be routed to the target service.
  6. If the token is missing or invalid, reject the request immediately with a 401 Unauthorized error.

Let's see how to implement this in code.

Implementing a Custom Authentication Filter

One of the most direct ways to implement gateway authentication is by creating your own custom filter. This gives you complete control over the validation logic and is an excellent way to understand the underlying mechanics.

The following video provides a detailed, step-by-step walkthrough of building this exact architecture. It involves creating a dedicated identity-service (like our Authorization Server from the previous lesson) and then implementing a custom filter in the API Gateway.

Microservices Security Using JWT | Spring Cloud Gateway | JavaTechie

The video "Microservices Security Using JWT" by Java Techie demonstrates the entire end-to-end flow. We will focus on the API Gateway implementation, which shows how to build and apply a custom authentication filter.

Please watch the following segments: Architecture Overview (1:31 - 6:01): This sets the stage and explains the high-level design, reinforcing the 'why' behind centralizing security at the gateway. Filter Implementation (55:50 - 1:16:44): This is the core of the lesson. Pay close attention to how the AuthenticationFilter class is created. Note how it checks for the Authorization header, extracts the token, and uses a JwtUtil class to perform the actual validation. Also, observe how this filter is applied to specific routes in the application.yml file. Live Demonstration (1:16:44 - 1:25:04): Watch how a request without a token is rejected by the gateway, and a request with a valid token is successfully routed to the downstream service. This visualizes the filter in action.

Key Takeaways from the Implementation

Let's summarize the crucial parts of the implementation shown in the video:

  1. AuthenticationFilter Class:

    • The filter extends AbstractGatewayFilterFactory. This is a common base class for creating custom filters in Spring Cloud Gateway.
    • The core logic resides in the apply method.
    • It uses a RouteValidator to define a whitelist of public endpoints (like /auth/register and /auth/token) that should bypass authentication.
  2. JWT Validation Logic:

    • The filter extracts the token from the Authorization: Bearer <token> header.
    • It reuses the exact same JWT parsing and validation logic (the JwtUtil class) from the identity-service. This is a critical point: the service that issues the token and the service that validates it must share the same algorithm, secret key, or public key.
    • If validation fails (e.g., signature is invalid, token is expired), an exception is thrown, which results in an error response to the client.
  3. Applying the Filter in application.yml:

    • The custom filter is enabled on a per-route basis. This gives you fine-grained control over which services are protected.
    spring:
      cloud:
        gateway:
          routes:
            - id: swiggy-app
              uri: lb://SWIGGY-APP
              predicates:
                - Path=/swiggy/**
              filters:
                - name: AuthenticationFilter # Our custom filter
            - id: identity-service
              uri: lb://IDENTITY-SERVICE
              predicates:
                - Path=/auth/**
    

    Notice the identity-service route does not have the authentication filter. This is essential, as clients need to be able to access it to get a token in the first place!

Test your understanding!

In the video's implementation, the JWT validation logic (the JwtUtil class) was copied from the identity-service into the api-gateway project. What is a major drawback of this "copy-paste" approach, and how could you improve it in a real-world project to avoid this issue?

Show answer

The major drawback is code duplication. If you ever need to change the signing secret, the token expiration logic, or the signing algorithm, you must remember to update it in both the identity-service and the api-gateway. Forgetting to do so would break authentication for the entire system.

A better approach would be to package the JwtUtil class and any related DTOs into a shared library (a separate Maven/Gradle module). Both the identity-service and api-gateway projects could then include this library as a dependency, ensuring they always use the exact same, single-source-of-truth implementation for JWT handling.

An Alternative: The Spring Security Integrated Approach

Writing a custom filter provides great insight, but for standard scenarios, Spring Security offers a more declarative and integrated way to achieve the same goal.

Remember how in the last lesson we made our microservice a Resource Server? We can apply the exact same concept to the API Gateway. By including the spring-boot-starter-oauth2-resource-server dependency in our gateway and configuring our SecurityWebFilterChain (the reactive equivalent of SecurityFilterChain), we can leverage Spring's built-in JWT validation capabilities.

JWT Auth in Spring Cloud Gateway: A Step-by-Step Guide

The article "JWT Auth in Spring Cloud Gateway" provides an excellent, concise example of this integrated approach. It contrasts the custom filter method with a more declarative one using Spring Security's native features.

Please read the introduction and then focus on the sections detailing the dependencies and Approach 1: Custom WebFilter. Notice how the JwtWebFilter and the SecurityConfig work together. This is a slightly different but conceptually similar take on the custom filter from the video.

The approach shown in the article uses a standard WebFilter and EnableWebFluxSecurity. A more streamlined version, when using a standard OIDC-compliant Authorization Server, would configure the gateway as a resource server directly:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/auth/**").permitAll() // Public endpoint
                .anyExchange().authenticated()       // All others require authentication
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt()); // Enable JWT validation

        return http.build();
    }
}

And in your application.yml, you'd simply point to the issuer URI, just as we did in the last lesson:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000 # Your Auth Server

Architectural Choice: Custom Filter vs. Integrated Security

This presents you with a key architectural decision, a perfect topic for an interview discussion.

  • Custom Filter Approach (like the video):

    • Pros: Maximum flexibility to handle non-standard tokens, custom validation rules, or complex logic that isn't just about signature/claim validation.
    • Cons: More boilerplate code to write and maintain. You are responsible for the security implementation.
  • Integrated Resource Server Approach:

    • Pros: Far less code. Highly declarative. Leverages a battle-tested Spring Security implementation. Automatically handles fetching public keys from a jwks_uri.
    • Cons: Less flexible if you have very specific, non-standard requirements. It assumes you are working with standard JWTs and an OAuth2/OIDC provider.

For most production systems using standard OAuth2, the Integrated Resource Server approach is preferred due to its simplicity and robustness.

Conclusion

You have now learned how to fortify the entry point to your microservices ecosystem. Centralizing authentication at the API Gateway is a powerful pattern that simplifies your overall architecture and strengthens your security posture.

Key Takeaways:

  • The API Gateway is the ideal place to handle cross-cutting concerns like authentication.
  • Spring Cloud Gateway's GatewayFilter mechanism allows you to intercept and validate requests before they reach downstream services.
  • You can implement authentication using a custom filter for maximum control or by configuring the gateway as an OAuth2 Resource Server for a more declarative, standard-compliant approach.
  • The choice between these patterns is a trade-off between flexibility and simplicity, a key consideration in system design.

Next Up

We've secured our system from external threats by validating tokens at the gateway. But what about the communication between our services? Should the order-service be able to call the inventory-service without any authentication? In our next lesson, we will answer this by exploring strategies for inter-service authentication, such as propagating JWTs or using mTLS.

Can't find a good explanation? Sign up and we'll make it for you

Sign up