2023-05-24 10:00:00+00:00

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.