Skip to main content

Overview

Custom token sources allow you to implement advanced token management strategies, such as:
  • Sharing cached tokens across multiple services
  • Integrating with external token providers
  • Implementing custom refresh logic to reduce M2M token usage
  • Adding custom authentication flows
The SDK accepts any oauth2.TokenSource implementation, giving you full control over token acquisition and caching.

Using Custom Token Sources

The WithTokenSource option allows you to provide your own token source implementation:
import (
    "context"
    "github.com/auth0/go-auth0/v2/management/client"
    "github.com/auth0/go-auth0/v2/management/option"
    "golang.org/x/oauth2"
)

mgmt, err := client.New(
    "your-tenant.auth0.com",
    option.WithTokenSource(oauth2.ReuseTokenSource(nil, myCustomTokenSource)),
)
The authentication options (WithClientCredentials, WithClientCredentialsAndAudience, WithClientCredentialsPrivateKeyJwt, WithClientCredentialsPrivateKeyJwtAndAudience, and WithTokenSource) are mutually exclusive. They all configure the underlying token source, so if multiple are provided, the last one applied takes effect.Similarly, WithToken sets a static token, but any token source option takes priority over it.

Example: Shared Token Cache

Implement a token source that shares tokens across multiple SDK instances:
package main

import (
    "context"
    "sync"
    "time"

    "github.com/auth0/go-auth0/v2/management/client"
    "github.com/auth0/go-auth0/v2/management/option"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/clientcredentials"
)

// SharedTokenCache implements oauth2.TokenSource with cross-instance caching
type SharedTokenCache struct {
    mu           sync.RWMutex
    token        *oauth2.Token
    config       *clientcredentials.Config
    refreshBuffer time.Duration
}

func NewSharedTokenCache(domain, clientID, clientSecret string) *SharedTokenCache {
    return &SharedTokenCache{
        config: &clientcredentials.Config{
            ClientID:     clientID,
            ClientSecret: clientSecret,
            TokenURL:     "https://" + domain + "/oauth/token",
            EndpointParams: url.Values{
                "audience": {"https://" + domain + "/api/v2/"},
            },
        },
        refreshBuffer: 5 * time.Minute, // Refresh 5 minutes before expiry
    }
}

func (s *SharedTokenCache) Token() (*oauth2.Token, error) {
    s.mu.RLock()
    if s.token != nil && s.token.Valid() {
        // Check if token is still valid with buffer
        if time.Until(s.token.Expiry) > s.refreshBuffer {
            token := s.token
            s.mu.RUnlock()
            return token, nil
        }
    }
    s.mu.RUnlock()

    // Acquire write lock for refresh
    s.mu.Lock()
    defer s.mu.Unlock()

    // Double-check after acquiring write lock
    if s.token != nil && time.Until(s.token.Expiry) > s.refreshBuffer {
        return s.token, nil
    }

    // Fetch new token
    ctx := context.Background()
    token, err := s.config.Token(ctx)
    if err != nil {
        return nil, err
    }

    s.token = token
    return token, nil
}

func main() {
    // Create shared token cache
    tokenCache := NewSharedTokenCache(
        "your-tenant.auth0.com",
        "YOUR_CLIENT_ID",
        "YOUR_CLIENT_SECRET",
    )

    // Use with ReuseTokenSource for automatic token refresh
    tokenSource := oauth2.ReuseTokenSource(nil, tokenCache)

    // Create multiple management clients sharing the same token
    mgmt1, err := client.New(
        "your-tenant.auth0.com",
        option.WithTokenSource(tokenSource),
    )

    mgmt2, err := client.New(
        "your-tenant.auth0.com",
        option.WithTokenSource(tokenSource),
    )

    // Both clients will use the same cached token
    ctx := context.Background()
    clients1, _ := mgmt1.Clients.List(ctx, nil)
    clients2, _ := mgmt2.Clients.List(ctx, nil)
}

Example: Redis-Backed Token Cache

Store tokens in Redis for sharing across distributed services:
package main

import (
    "context"
    "encoding/json"
    "time"

    "github.com/go-redis/redis/v8"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/clientcredentials"
)

type RedisTokenSource struct {
    redis        *redis.Client
    cacheKey     string
    config       *clientcredentials.Config
    refreshBuffer time.Duration
}

func NewRedisTokenSource(
    redisClient *redis.Client,
    cacheKey string,
    domain string,
    clientID string,
    clientSecret string,
) *RedisTokenSource {
    return &RedisTokenSource{
        redis:    redisClient,
        cacheKey: cacheKey,
        config: &clientcredentials.Config{
            ClientID:     clientID,
            ClientSecret: clientSecret,
            TokenURL:     "https://" + domain + "/oauth/token",
            EndpointParams: url.Values{
                "audience": {"https://" + domain + "/api/v2/"},
            },
        },
        refreshBuffer: 5 * time.Minute,
    }
}

func (r *RedisTokenSource) Token() (*oauth2.Token, error) {
    ctx := context.Background()

    // Try to get token from Redis
    data, err := r.redis.Get(ctx, r.cacheKey).Bytes()
    if err == nil {
        var token oauth2.Token
        if err := json.Unmarshal(data, &token); err == nil {
            if time.Until(token.Expiry) > r.refreshBuffer {
                return &token, nil
            }
        }
    }

    // Fetch new token
    token, err := r.config.Token(ctx)
    if err != nil {
        return nil, err
    }

    // Cache in Redis with expiry
    tokenData, _ := json.Marshal(token)
    ttl := time.Until(token.Expiry)
    r.redis.Set(ctx, r.cacheKey, tokenData, ttl)

    return token, nil
}

func main() {
    // Initialize Redis client
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // Create Redis-backed token source
    tokenSource := NewRedisTokenSource(
        rdb,
        "auth0:management:token",
        "your-tenant.auth0.com",
        "YOUR_CLIENT_ID",
        "YOUR_CLIENT_SECRET",
    )

    // Use with management client
    mgmt, err := client.New(
        "your-tenant.auth0.com",
        option.WithTokenSource(oauth2.ReuseTokenSource(nil, tokenSource)),
    )
}

Example: External Token Provider

Integrate with an external token management service:
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "golang.org/x/oauth2"
)

type ExternalTokenProvider struct {
    tokenServiceURL string
    apiKey         string
    httpClient     *http.Client
}

func NewExternalTokenProvider(serviceURL, apiKey string) *ExternalTokenProvider {
    return &ExternalTokenProvider{
        tokenServiceURL: serviceURL,
        apiKey:         apiKey,
        httpClient:     &http.Client{Timeout: 10 * time.Second},
    }
}

func (e *ExternalTokenProvider) Token() (*oauth2.Token, error) {
    req, err := http.NewRequest("GET", e.tokenServiceURL+"/token/auth0", nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+e.apiKey)
    req.Header.Set("Accept", "application/json")

    resp, err := e.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("token service returned status: %d", resp.StatusCode)
    }

    var tokenResp struct {
        AccessToken string    `json:"access_token"`
        TokenType   string    `json:"token_type"`
        ExpiresAt   time.Time `json:"expires_at"`
    }

    if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
        return nil, err
    }

    return &oauth2.Token{
        AccessToken: tokenResp.AccessToken,
        TokenType:   tokenResp.TokenType,
        Expiry:      tokenResp.ExpiresAt,
    }, nil
}

func main() {
    // Create external token provider
    tokenProvider := NewExternalTokenProvider(
        "https://token-service.example.com",
        "YOUR_API_KEY",
    )

    // Use with management client
    mgmt, err := client.New(
        "your-tenant.auth0.com",
        option.WithTokenSource(oauth2.ReuseTokenSource(nil, tokenProvider)),
    )
}

Best Practices

Use ReuseTokenSource

Always wrap your custom token source with oauth2.ReuseTokenSource to automatically handle token refresh and avoid unnecessary token requests.

Implement Refresh Buffer

Refresh tokens before they expire (e.g., 5 minutes early) to avoid authentication failures during requests.

Thread Safety

Make your token source implementation thread-safe using mutexes or channels, as it may be called concurrently.

Error Handling

Implement proper error handling and retry logic for token acquisition failures.

Static Tokens

For simple use cases where you already have a valid token, use WithToken:
mgmt, err := client.New(
    "your-tenant.auth0.com",
    option.WithToken("YOUR_ACCESS_TOKEN"),
)
Static tokens don’t automatically refresh. For production use, prefer token sources that handle refresh automatically.

Build docs developers (and LLMs) love