Spring Boot Handbook

    Microservice Advance: Authentication in the API Gateway using custom Gateway Filters

    Introduction#

    In a microservices architecture, efficient management of authentication and security is essential. The common practice is to do the authentication at the API Gateway, ensuring that only authorized requests are passed to the backend services. This approach centralizes the security logic, decreases duplicative authentication checks among microservices, and leads to performance optimization.

    This article discusses implementing an API Gateway authentication with custom Gateway Filters in Spring Cloud Gateway.

    Key points of discussion will be:#

    • Role of API Gateway in Microservices.
    • Ways to do authentication at the gateway.
    • Creating custom gateway filters for request validation.
    • Integrate with JWT to enable secure authentication.

    Upon finishing this guide, you'll be comfortable working with a secure API Gateway that provides authentication and secures microservices from unauthorized access.

    Gateway Filters

    Real Time Analogy#

    With a security checkpoint, an API gateway acts just like a gated community: it serves as the sole authentication mechanism between a user and a microservice. Pre-filters validate requests such as visitor ID checks before routing them to the appropriate service and modifying headers. On the other hand, post-filters are those which modify responses such as logging exit times or enforcing policies. This centralized approach ensures effective security, scalability, and even seamless request handling in a microservices architecture.

    API Gateway Authentication: What is API Gateway Authentication?#

    API Gateway authentication is a centralized security element that authenticates all incoming requests before they are routed to backend microservices. This enhances security, scalability, and performance in a microservices architecture since it is sure that only authenticated and authorized users can access secure resources.

    How does API Gateway Authentication function?#

    • Clients authenticate themselves only once at the API Gateway using JWT, OAuth2, API keys, or any other authentication mechanism.
    • The API Gateway accepts the given credentials and extracts user identity information.
    • The authenticated request is forwarded to the proper microservice, along with user credentials or a token for the latter's serialization.

    Why Use API Gateway for Authentication?#

    • Centralized Security: A more secure option centralizes a security layer and cuts on the framework.
    • Improved Performance: It cuts redundant authentication checks among the services, allowing a performance boost.
    • Scalability and Load Balancing: this is an effective solution for directing traffic to specific microservices and spreading requests fairly evenly between them.
    • Integration with JWT & OAuth2: It supports the booming technologies of today that guarantee secure application development.

    Implementing API Gateway Authentication with Custom Filters#

    Custom Class:

    package com.codingshuttle.ecommerce.api_gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; @Component @Slf4j public class AuthenticationFilter extends AbstractGatewayFilterFactory<AuthenticationFilter.Config> { private final JwtService jwtService; public AuthenticationFilter(JwtService jwtService) { super(Config.class); this.jwtService = jwtService; } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { if(!config.enabled) return chain.filter(exchange); String authorizationHeader = exchange.getRequest().getHeaders().getFirst("Authorization"); if (authorizationHeader == null) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } String jwtToken = authorizationHeader.split("Bearer ")[1]; Long userId = jwtService.getUserIdFromToken(jwtToken); ServerHttpRequest mutatedRequest = exchange.getRequest() .mutate() .header("X-User-Id", userId.toString()) .build(); return chain.filter(exchange.mutate().request(mutatedRequest).build()); }; } @Data public static class Config{ private boolean enabled; } }

    To enable JWT built-in classes, methods all, we need to add the required dependencies.

    <!-- security --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>

    Jwt Service Class:

    We need a service class to generate tokens and extract the user ID from them. For further details you can check out https://www.codingshuttle.com/spring-boot-hand-book/jwt-creation-and-verification/.

    package com.codingshuttle.ecommerce.api_gateway.services; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; @Service public class JwtService { @Value("${jwt.secretKey}") //put the value from .yml file private String jwt_Secret_Key; private SecretKey getSecretKey() { return Keys.hmacShaKeyFor(jwt_Secret_Key.getBytes(StandardCharsets.UTF_8)); } public Long getUserIdFromToken(String token) { Claims claims = Jwts.parser() .verifyWith(getSecretKey()) .build() .parseSignedClaims(token) .getPayload(); return Long.valueOf(claims.getSubject()); } }

    Api-Gateway yml file:

    StripPrefix=2 is a route-specific filter that removes the first two segments of the URL path before forwarding the request. Global filters, such as authentication and logging, are automatically applied to all routes and do not require additional configuration.

    #Security signature jwt: secretKey: mccnddkhdhdshohowhdisdsanklsnlnhasudiuiudsuhdsh #api-gateway config spring: cloud: gateway: routes: - id: order-service uri: lb://ORDER-SERVICE predicates: - Path=/api/v1/orders/** filters: - StripPrefix=2 - name: LoggingOrderFilter - name: AuthenticationFilter args: enabled: true
    This is the full api-gateway configuration

    Here I applied this authentication using jwt inside order-service route which I also configured inside api-gateway.

    Order Service Controller:

    package com.codingshuttle.ecommerce.order_service.controllers; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/core") public class OrderController { @GetMapping("/helloOrders") public String helloOrders(@RequestHeader("X-User-Id") Long userId) { //we pass the request header return "Hello from OrderService, user id is: " + userId; } }

    Output#

    • Run all services:

    After running all services (the sequence is 1. Run Eureka Server (Discovery Service) 2. Run other clients services. 3. Last run gateway service on default port 8080)

    • Wait for all registered client services inside Eureka:

    if your services are not fully registered before another service tries to communicate with them, you may encounter a "Service Unavailable" exception.

    After starting Eureka and all client services, test the API using a browser (for GET requests) or Postman (for all requests). If you are trying to fetch other services you can easily fetch it. Here, we are trying to fetch inventory service data by using api-gateway (port=8080). /api/v1 ignored because of using StripPrefix=2 .

    Getting “401 UNAUTHORIZED”.

    401 UNAUTHORIZED
    Ide Console

    For authentication you need a valid token (without valid signature {your secrete key}). For that go to https://jwt.io/

    Jwt token

    Put this token inside the postman’s header (without valid signature). You will get a “500 Server Error”

    500 Server Error
    Ide Console
    • After putting valid signature (value of the secrete key).
    #Security signature jwt: secretKey: mccnddkhdhdshohowhdisdsanklsnlnhasudiuiudsuhdsh
    Jwt token
    • Use this token inside header. To get the actual output.
    Fetching user id

    Mutating the Requests: Authentication and Request Mutation with API Gateway#

    What is Request Mutation?#

    A request mutation is an API request that has been modified by the time it reaches a specific microservice.

    Pre-filtering mechanisms are set up generally at the API Gateway itself to enable request header, request path, or query parameter modifications without making changes to the microservices themselves.

    Common Use Cases for Request Mutation#

    • Add or change HTTP headers: Include authentication tokens, tracking information, or metadata.
    • Rewrite request paths: Make dynamic API routing changes to match internal service paths.
    • Alteration of query parameters: Additional parameters are added or modified for logging, analytics, or A/B testing.

    The rationale for API Gateway Request Mutation#

    • Centralized request processing: Since it offloads the request transformation logic from microservices, it keeps microservices lightweight.
    • Enhanced security: Guarantees that the safety of authentication tokens and security policies is not compromised before reaching the backend services.
    • Better flexibility: Allows dynamic request changes without carrying out modifications for many other microservices.

    1. API Gateway as an Authentication Layer#

    All requests should arrive and depart through an API Gateway. A client request is directed from the user's client device to an API Gateway endpoint to process that request, among other things. Thus, it acts as a central authentication point by validating client credentials before forwarding requests. Authentication may include one of the following methods.

    • JWT (JSON Web Token): Clients append a token, which the gateway validates.
    • OAuth2: The Gateway checks access tokens assigned by an authorization server.
    • Custom Authentication: The Gateway authenticates user credentials and injects user-specific data into request headers.

    Example: Adding an Authentication Header

    exchange.mutate() .request(builder -> builder .header("Authorization", "Bearer " + jwtToken) // Inject the JWT token .build());

    In this example, the API Gateway adds an Authorization header to ensure only authenticated requests reach backend microservices.

    2. Mutating Requests in API Gateway#

    Request mutation refers to modifying request bodies, headers, or the path before passing them to downstream services. Typically, such modifications are done using pre-filters in Spring Cloud Gateway.

    Common Use Cases of Request Mutation

    • **Adding Headers—**Injecting authentication tokens or user details.
    • **Modifying the Path—**Dynamically changing the request URL.
    • **Changing Query Parameters—**Adding or modifying query parameters.

    Example: Adding a Custom Header with User ID

    Long userId = jwtService.getUserIdFromToken(token); ServerHttpRequest mutatedRequest = exchange.getRequest() .mutate() .header("X-User-Id", userId.toString()) // Add a custom header .build(); return chain.filter(exchange.mutate().request(mutatedRequest).build());

    In this case, the API Gateway adds an "X-User-Id" header, which downstream services can use for authorization or tracking.

    3. Mutating the Request Path#

    Sometimes, you may need to rewrite the request URL before forwarding it.

    Example: Changing the Request Path Dynamically

    Long userId = jwtService.getUserIdFromToken(token); return chain.filter(exchange.mutate().request(builder -> builder .method(HttpMethod.GET) .header("X-User-Id", userId.toString()) .path("/orders/core/helloOrders") ).build());

    Noted: Since Spring Boot 3.4.0+, ServerWebExchange and ServerHttpRequest have become strictly immutable, meaning any modification requires creating a new instance.

    Mutating the Response#

    API Gateway response transformation simply ensures that responses returned by the APIs do not reach the client directly. These responses are altered mostly in post-filters so that they allow for security, consistency, and flexibility in a microservices architecture.

    Key Aspects of Response Transformation:#

    • Security Headers: CORS, CSP, and Auth headers were added to increase API security.
    • Modify Response Body: JSON structuring, masking of sensitive data, and attaching additional metadata to the response as required.
    • Standardization of Error Replies: The error message formats should be uniform across all of the microservices.
    • Log and Monitor enhancement: API responses can now be appended with tracking IDs that can be injected as analytics data.

    1. Adding Custom Headers to the Response (Adding a Security Header to the Response)#

    ServerHttpResponse mutatedResponse = exchange.getResponse().mutate() .headers(httpHeaders -> httpHeaders.add("X-Processed-By", "API-Gateway")) .build(); ServerWebExchange mutatedExchange = exchange.mutate() .response(mutatedResponse) .build(); // Creating a new immutable exchange return chain.filter(mutatedExchange);

    This ensures that every response contains the "X-Processed-By" header to indicate that it was handled by the API Gateway.

    2. Modifying the Response Body (Transforming JSON Response)#

    @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("modify_response_body", r -> r.path("/process-response") .filters(f -> f.modifyResponseBody(String.class, String.class, (exchange, body) -> { String modifiedBody = body.toUpperCase(); // Convert response to uppercase return Mono.just(modifiedBody); })) .uri("<http://downstream-service>")) .build(); }

    Here, the modifyResponseBody filter intercepts the response and applies modifications.

    3. Modifying Response Status Code (Changing Status Code Based on Conditions)#

    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // Change status code to 401 return chain.filter(exchange);

    Conclusion#

    This article describes using advanced authentication in an API Gateway to offer custom gateway filter features that would provide security to microservices. It also covers centralized authentication using JWT/OAuth2, the mutation of requests and responses, and how custom filters enhance scalability and maintenance. It also illustrates how Spring Cloud Gateway allows developers to build secure, efficient, and flexible microservices architecture.

    Last updated on Mar 05, 2025