OAuth 2.0

Securing Microservices with OAuth 2.0 and Keycloak

Auth in microservices? It’s usually a mess. You’ve got JWTs flying everywhere, logic duplicated across services, and the constant fear of token leaks.

I recently had to lock down a bunch of Node.js, Python, and Go microservices without rewriting everything. The solution? OAuth 2.0 for flows and Keycloak for centralized identity and access management.

The goal: centralized auth, token-based access, zero shared secrets, and minimal rewrites. Here’s how it worked.

Setting-up for the OAuth 2.0 project

Infrastructure

  • Microservices: Dockerized
  • API Gateway: NGINX with rate limiting
  • Keycloak v22: Quarkus distribution, Dockerized
  • Database: PostgreSQL for Keycloak
  • HTTPS: Self-signed certs for dev, HTTPS proxy at the edge

Client Apps

  • Frontend: React + Axios
  • Testing Tools: Postman, curl, Python scripts

Roles

  • admin
  • user
  • service (for internal comms)

Protocols Used

  • Authorization Code Flow: For frontend auth
  • Client Credentials Flow: For internal service-to-service calls
  • Bearer Tokens: Short-lived (5 mins), refresh tokens for browsers

Token Flow Overview

  1. Frontend redirects user to Keycloak for login
  2. After auth, the frontend receives a JWT access token
  3. Token is sent via Authorization: Bearer <token> header to backend
  4. Backend validates token using Keycloak’s JWKS endpoint (public key)
  5. If token is valid + scope OK → request is processed

Service-to-Service Authentication

OAuth 2.0
  • Each microservice is a registered client in Keycloak
  • Services use Client Credentials Flow to fetch tokens: bashCopyEditclient_id + client_secret → token
  • Tokens are scoped to internal roles only
  • API Gateway + individual services enforce access rules based on token claims

Keycloak Configuration

  • Created new realm: microservice-prod
  • Registered clients: frontend-ui, user-api, data-api, etc.
  • Defined roles per service
  • Token lifespan: 5 minutes
  • Refresh tokens: enabled for browser clients
  • JWKS endpoint used for signature validation — no key sharing
  • Stateless: No session storage

React Frontend Notes

  • Tokens are stored in memory only
  • Silent token refresh via iframe every 4 mins
  • Logging out removes tokens from both client and Keycloak session

Service Validation Logic

Each service:

  • Validates JWT signature using public key
  • Verifies:
    • aud (audience)
    • scope
    • exp (expiry)
  • Rejects anything with:
    • Wrong role
    • Expired token
  • Logs failed attempts (IP + reason)

Load Testing Results

  • Setup: 5 microservices
  • Traffic: ~800 API calls/hr
  • Peak Load Test: No token drops
  • One failure: token reuse post-expiry → added retry logic

Best Practices

> Don’t use localStorage for tokens — XSS risk
> Use short-lived access tokens, refresh tokens only for frontends
> Separate internal and external clients
> Always validate issuer, audience, and expiry
> Rotate client_secrets every 90 days
> Log auth failures (IP + token ID, never full token)
> Never trust tokens blindly — validate signature
> Cache JWKS keys, but refresh on 401s (for key rotation)
> Use custom Keycloak themes for user-facing apps
> Wrap auth logic in shared modules to avoid duplication

Conclusion

Locking down a multi-language microservices stack with Keycloak and OAuth 2.0 was totally worth it.

Auth flows are centralized, services don’t manage users anymore, and there’s no more hand-rolled token validation code. Tokens are scoped, short-lived, and validated per request.

Keycloak takes a bit to learn, but once it’s wired up, it just works. Next steps: automate client provisioning, improve audit logging, and maybe explore token introspection for finer-grained access.

>No shared secrets
>No session state
>No custom auth junk
> Just clean, predictable OAuth

Read more posts:- See Your Network in a Whole New Light: Building a Live Traffic Dashboard with P4 and Grafana

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

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