When an application grows to integrate multiple external identity providers (like Google, Facebook, Apple, and corporate SAML), managing these tokens inside individual microservices becomes unmanageable. Downstream microservices should not need to understand 5 different identity formats.
The solution is a Token-Exchange Gateway. The gateway acts as a security translation layer: it consumes external authentication tokens, verifies them, and issues a unified, custom-signed internal JWT for downstream services.
1. Token Translation Flow
The token-exchange flow follows these steps:
- The client authenticates with the external provider (e.g., Google OAuth) and receives an ID token.
- The client sends this ID token to the Token-Exchange Gateway.
- The gateway validates the signature and claims using the provider's JWKS endpoint.
- The gateway queries the internal User database to map the external ID to the internal system UUID.
- The gateway generates a new JWT signed with the gateway's private key, containing standard claims (roles, permissions, userID).
2. Fast Signature Verification with JWKS Cache
To avoid hitting external identity providers on every token exchange, we cache their JWKS public keys in memory with a cache expiry window (e.g., 24 hours):
type JWKSCache struct {
keys map[string]*rsa.PublicKey
mu sync.RWMutex
url string
}
func (c *JWKSCache) GetKey(kid string) (*rsa.PublicKey, error) {
c.mu.RLock()
key, exists := c.keys[kid]
c.mu.RUnlock()
if exists {
return key, nil
}
// Lock and refresh keys from provider URL
return c.refreshAndGetKey(kid)
}
By verifying external tokens once at the gateway, downstream microservices only need to verify one signature key, minimizing CPU overhead and database requests.