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
- Frontend redirects user to Keycloak for login
- After auth, the frontend receives a JWT access token
- Token is sent via
Authorization: Bearer <token>
header to backend - Backend validates token using Keycloak’s JWKS endpoint (public key)
- If token is valid + scope OK → request is processed
Service-to-Service Authentication

- Each microservice is a registered client in Keycloak
- Services use Client Credentials Flow to fetch tokens: bashCopyEdit
client_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