0%

Microservice Gateway Routing: OAuth2 Redirects and Service Decomposition

🌐 Language: English Version | 中文版

In microservice architectures, the API Gateway serves as the unified governance layer for all incoming traffic. Its routing design directly impacts system maintainability and evolution capabilities. This article documents a real-world scenario involving OAuth2 redirect issues during gateway integration with Spring Security, and how we achieved seamless client experience and high backend cohesion through Context-Path configuration and BFF (Backend For Frontend) patterns during service decomposition.

The Problem: Scattered Routes Creating Architectural Debt

Initial Scenario

When debugging our gateway locally, we discovered that accessing sample applications through the gateway (e.g., http://localhost:18000/java-example) and clicking the “Login with Enterprise IdP” button resulted in a 404 Not Found error when redirecting to the gateway’s /login path.

First Fix: The Patch Approach

To quickly resolve this, we implemented a “patch” solution by hardcoding scattered routes in the gateway configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Scattered routes configuration in gateway
routes:
- paths: ["/login"]
service: auth-service
preserve_host: true
strip_path: false
- paths: ["/oauth2"]
service: auth-service
preserve_host: true
strip_path: false
- paths: ["/css", "/js", "/error"]
service: auth-service
preserve_host: true
strip_path: false

Result: While the login flow worked, this scattered routing configuration at the gateway’s root level felt fragile. In production operations, such scattered routes would lead to:

  • High maintenance costs: Each new path requires manual configuration, prone to omissions and errors
  • Configuration chaos: As business grows, routing rules quickly become bloated and difficult to manage
  • Security risks: Unified access control becomes challenging, potentially enabling path traversal or unauthorized access
  • Poor scalability: Service decomposition or path adjustments require extensive gateway configuration changes, hindering rapid iteration

Unified Prefix Approach: Redirect Problem Analysis

Since scattered routes are messy, can we unify /login, /oauth2, and other scattered configurations under a /auth prefix at the gateway level?

The answer is no—this introduces redirect problems.

Root Cause Analysis

Frameworks like Spring Security, when constructing page URLs or generating absolute Location redirect responses (302 redirects) sent to clients, if unaware they’re behind a gateway’s /auth prefix, will still generate links based on the root path /oauth2/authorization/....

sequenceDiagram
    participant Browser as Browser
    participant Gateway as API Gateway
    participant Service as auth-service (Spring Security)

    Browser->>Gateway: GET /auth/login
    Gateway->>Service: GET /auth/login (strip_path: false)
    Service-->>Gateway: 302 Location: /oauth2/authorization/idp
    Gateway-->>Browser: 302 Location: /oauth2/authorization/idp
    Browser->>Gateway: GET /oauth2/authorization/idp
    Gateway-->>Browser: 404 Not Found (route doesn't exist)

Key Conclusion: Frontend and backend contexts must be strongly aligned! Never hide the prefix on one side while exposing it on the other.


Context-Path and Service Decomposition Compatibility

The Canonical Solution: Server-Side Context-Path Configuration

To align contexts, the solution is to configure the Java backend service directly:

1
2
3
4
5
# application.yml
server:
servlet:
context-path: /auth # Context-Path defines the web application's root path.
# When set to /auth, all requests to this service start with /auth, e.g., /auth/login.

API Compatibility After Service Decomposition

This raises a core question in microservice evolution: If the service prefix is hardcoded at the code level, when future business changes require service splitting, do client API URLs need to change as well?

If clients are forced to upgrade their code because of backend decomposition, doesn’t this defeat the purpose of the gateway’s anti-corruption layer?

Gateway Routing Priority Design

Through deep analysis, we arrived at the core API management philosophy for microservices:

Microservices should never care about their external names. The API contract published externally (e.g., http://gateway/auth/users) is entirely the governance asset of the API Gateway.

If we decompose some interfaces to a new service, clients don’t need to change—they still access the old /auth/new-feature.

Backend Development: Add a high-priority exact prefix hijacking route in the gateway:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
routes:
# Generic route (low priority)
- name: route-iam
service: auth-service
paths:
- /auth
strip_path: false # Client requests /auth/login, backend receives /auth/login

# Exact hijacking route (high priority)
- name: route-new-feature
service: new-service
paths:
- /auth/new-feature
strip_path: true # Client requests /auth/new-feature/api, backend receives /api
# More specific paths (/auth/new-feature) are matched first

Configuration Strategy:

Route Type Path Config strip_path Priority Description
Generic Route /auth false Low Handles most auth-service requests
Hijacking Route /auth/new-feature true High Exact match, intercepts specific sub-paths and forwards to new service

With this combination, client code requires no modifications, achieving service decomposition through the gateway.


BFF Pattern and OAuth2 Security Best Practices

BFF (Backend For Frontend) is an architecture pattern where backends are tailored for specific frontend applications (Web, iOS, Android). It sits between traditional backend services and frontends, responsible for aggregating and transforming data, and handling frontend-specific business logic to optimize user experience and development efficiency.

Pure Frontend Login Approach Considerations

Since handling Spring Boot page endpoints with redirects is complex, can we separate the login page into a pure frontend microservice (like an independently deployed Vue/React container) to avoid backend redirect binding logic?

Browser-Side OAuth2 Security Risks

Completing the OAuth2 loop and storing tokens purely in the browser presents serious security risks:

  • Implicit Flow: Directly obtaining Access Tokens in the browser is vulnerable to XSS attacks and token leakage, and has been deprecated in OAuth 2.1.
  • PKCE (Proof Key for Code Exchange): An enhancement to the authorization code flow to prevent authorization code interception attacks, currently the recommended standard practice, but token storage and usage must still be completed on the backend, not purely in the frontend.

Under current security standards and best practices, frontends should not directly hold Access Tokens.

BFF Pattern Best Practices

Core Principle: OAuth2 authorization code exchange must be completed by the backend; frontends should not hold tokens. The handshake with external IdPs (enterprise identity providers) to exchange authorization codes and Access Tokens should be handled at the backend BFF layer (i.e., auth-service).

sequenceDiagram
    participant Browser as Browser
    participant BFF as auth-service
    participant IdP as Enterprise IdP
    participant Cache as Redis/Cache

    Note right of Browser: 1. User clicks login
    Browser->>BFF: GET /auth/login
    BFF-->>Browser: Returns login page

    Note right of Browser: 2. User clicks Enterprise IdP login
    Browser->>BFF: GET /auth/oauth2/authorization/idp
    BFF->>IdP: 302 Location: https://idp.example.com/...

    Note right of Browser: 3. User completes authentication at IdP
    IdP-->>BFF: GET /auth/login/oauth2/code/idp?code=...
    BFF->>IdP: POST /token
    IdP-->>BFF: access_token, refresh_token

    Note right of BFF: 4. Store tokens in distributed cache
    BFF->>Cache: SET session_id {tokens}
    BFF-->>Browser: Set-Cookie: SESSION_ID

Key Security Measures:

  1. BFF stores tokens in distributed cache (e.g., Redis) after obtaining them
  2. Only issue an HttpOnly + Secure Cookie key to the user’s browser externally
  3. Cookies should not be readable by JS, preventing XSS theft
  4. Frontend uses this invisible same-domain cookie key to make all seamless calls
  5. Production environments recommend using distributed caches like Redis to share Session, avoiding Session inconsistency in multi-instance deployments

Context-Path Configuration Strategy

Authentication BFF Service Configuration

1. Add to auth-service’s application.yml:

1
2
3
server:
servlet:
context-path: /auth

2. Clean up redundant authentication routes (/login, etc.) in gateway configuration.

3. Restore the single /auth route mapping in the gateway, changing strip_path to false:

1
2
3
4
5
6
routes:
- name: route-iam
service: auth-service
paths:
- /auth
strip_path: false # Key: preserve /auth prefix

4. Example client targets the single authentication handshake endpoint:

1
http://gateway:18000/auth/login

Architecture Extension: Do Pure API Microservices Need Context-Path?

For pure API microservices without authorization pages and redirect logic (no absolute redirect concepts), they absolutely do not need Context-Path configuration!

Develop directly at the default root path /, letting the API Gateway handle all path merging, prefix trimming, and assembly strategies with strip_path: true, keeping services as pure API interface nodes.

Service Type Needs Context-Path strip_path Reason
Authentication BFF Yes false Needs to handle redirects; frontend and backend contexts must align
Pure API Microservice No true No redirect logic; gateway handles path trimming, service stays pure

Summary

In microservice gateway routing design, the core principle is: Find the boundary between gateway control plane and backend logic plane, making the gateway a truly powerful anti-corruption layer.

Key Decision Tree:

flowchart TD
    A[Microservice Routing Design] --> B{Service Type?}
    B -->|Authentication BFF| C[Configure Context-Path]
    B -->|Pure API Microservice| D[No Context-Path]
    C --> E[Gateway strip_path: false]
    D --> F[Gateway strip_path: true]
    E --> G[Align frontend and backend contexts]
    F --> H[Keep services pure]
    G --> I{Future decomposition?}
    H --> I
    I -->|Needed| J[Gateway exact prefix hijacking]
    I -->|Not needed| K[Maintain status quo]

Core Takeaways:

  1. Frontend and backend contexts must be strongly aligned: Services with redirects must configure Context-Path; gateway doesn’t trim prefix
  2. Gateway owns the API contract: Services don’t care about their external names; API contracts are governed by the gateway
  3. Service decomposition is transparent to clients: Through gateway exact prefix hijacking, clients don’t need code changes when backends decompose
  4. BFF pattern handles OAuth2: Authorization code exchange happens on the backend; frontend doesn’t hold tokens

“In the face of any architectural dilemma, finding the boundary between gateway control plane and backend logic plane is always where the anti-corruption layer delivers its greatest power!”