In a distributed microservice network, securing APIs from unauthorized internal calls is just as important as securing external endpoints. Machine-to-Machine (M2M) authentication utilizing the OAuth 2.0 Client Credentials Grant provides a robust framework for identifying and authorizing service-to-service communication.
Using Auth0 as the Identity Provider, services can exchange their client ID and secret for a signed JWT, which is then attached to all outbound requests.
1. Caching and Reusing Access Tokens
Requesting a new M2M token from Auth0 on every inter-service call introduces unacceptable network latency and will quickly hit rate limits. The Go client must cache the token in memory and only request a new one when the cached token is close to expiry:
type M2MTokenProvider struct {
client *http.Client
tokenURL string
clientID string
clientSecret string
audience string
cachedToken string
expiresAt time.Time
mu sync.RWMutex
}
func (p *M2MTokenProvider) GetToken(ctx context.Context) (string, error) {
p.mu.RLock()
if p.cachedToken != "" && time.Now().Before(p.expiresAt) {
defer p.mu.RUnlock()
return p.cachedToken, nil
}
p.mu.RUnlock()
// Acquire writer lock to refresh
p.mu.Lock()
defer p.mu.Unlock()
// Re-verify in case another thread refreshed it
if time.Now().Before(p.expiresAt) {
return p.cachedToken, nil
}
token, duration, err := p.fetchNewToken(ctx)
if err != nil {
return "", err
}
p.cachedToken = token
p.expiresAt = time.Now().Add(duration - 5*time.Minute) // buffer
return token, nil
}
2. Middleware Validation
On the receiving service, a Go middleware intercepts incoming requests, parses the Bearer JWT, and verifies its signature against Auth0's JWKS (JSON Web Key Set) endpoint. This ensures that only authorized services can invoke the endpoint.