> For the complete documentation index, see [llms.txt](https://docs.partssource.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.partssource.com/api/authentication/oauth-2.0-flow/best-practices-and-error-handling.md).

# Token Management

Best practices for token management and handling authentication errors

This guide covers best practices for managing access tokens, handling token refresh, and troubleshooting authentication errors.

***

## Token Caching

**Always cache tokens** to avoid unnecessary token requests. Tokens are valid for 1 hour.

### Recommended Strategy

1. **Cache the token** after successful retrieval
2. **Track expiration** using the `expires_in` value
3. **Refresh proactively** before expiration (5-minute buffer recommended)
4. **Handle failures** by requesting a new token

```csharp
public class TokenCache
{
    private string _token;
    private DateTime _expiry;
    private readonly SemaphoreSlim _lock = new(1, 1);

    public async Task<string> GetTokenAsync(Func<Task<TokenResponse>> requestToken)
    {
        // Check if token is valid (with 5-minute buffer)
        if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _expiry.AddMinutes(-5))
        {
            return _token;
        }

        await _lock.WaitAsync();
        try
        {
            // Double-check after acquiring lock
            if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _expiry.AddMinutes(-5))
            {
                return _token;
            }

            var response = await requestToken();
            _token = response.AccessToken;
            _expiry = DateTime.UtcNow.AddSeconds(response.ExpiresIn);

            return _token;
        }
        finally
        {
            _lock.Release();
        }
    }
}
```

### Why Cache Tokens?

| Without Caching              | With Caching             |
| ---------------------------- | ------------------------ |
| Token request every API call | One request per hour     |
| Added latency (\~100-300ms)  | No extra latency         |
| Risk of rate limiting        | Minimal auth server load |

***

## Token Refresh Strategies

### Proactive Refresh

Refresh the token before it expires to avoid failed API calls:

```typescript
class TokenManager {
  private refreshTimer: NodeJS.Timeout | null = null;

  async initialize() {
    await this.refreshToken();
  }

  private async refreshToken() {
    const response = await this.requestToken();
    this.accessToken = response.access_token;

    // Schedule next refresh 5 minutes before expiry
    const refreshIn = (response.expires_in - 300) * 1000;
    this.refreshTimer = setTimeout(() => this.refreshToken(), refreshIn);
  }

  destroy() {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
  }
}
```

### Reactive Refresh

Refresh when you receive a `401 Unauthorized` response:

```python
class ApiClient:
    def __init__(self, token_service):
        self.token_service = token_service
        self._token = None

    def request(self, method, url, **kwargs):
        if not self._token:
            self._token = self.token_service.get_token()

        headers = kwargs.get('headers', {})
        headers['Authorization'] = f'Bearer {self._token}'
        kwargs['headers'] = headers

        response = requests.request(method, url, **kwargs)

        # If unauthorized, refresh token and retry once
        if response.status_code == 401:
            self._token = self.token_service.get_token(force_refresh=True)
            headers['Authorization'] = f'Bearer {self._token}'
            response = requests.request(method, url, **kwargs)

        return response
```

***

## Security Best Practices

### 1. Secure Credential Storage

{% hint style="danger" %}
**Never hardcode credentials** in source code or commit them to version control.
{% endhint %}

**Recommended approaches:**

| Environment | Storage Method                                                          |
| ----------- | ----------------------------------------------------------------------- |
| Development | Environment variables, `.env` files (gitignored)                        |
| Production  | Secret managers (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) |
| CI/CD       | Pipeline secrets, secure variables                                      |

```csharp
// Good: Load from environment
var clientId = Environment.GetEnvironmentVariable("PARTSSOURCE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("PARTSSOURCE_CLIENT_SECRET");

// Bad: Hardcoded credentials
var clientId = "186ve3v505rpq71vi24br8c6e3"; // DON'T DO THIS
```

### 2. Use HTTPS Only

All API communication must use HTTPS. HTTP requests will be rejected or redirected.

### 3. Validate Token Responses

Always check for errors in token responses:

```typescript
const response = await fetch(tokenEndpoint, options);

if (!response.ok) {
  const error = await response.json();
  throw new AuthenticationError(
    `Token request failed: ${error.error} - ${error.error_description}`
  );
}
```

### 4. Implement Token Isolation

If your application serves multiple clients, isolate tokens:

```csharp
public class MultiTenantTokenService
{
    private readonly ConcurrentDictionary<string, TokenCache> _tokensByClient = new();

    public async Task<string> GetTokenAsync(string clientId, string clientSecret)
    {
        var cache = _tokensByClient.GetOrAdd(clientId, _ => new TokenCache());
        return await cache.GetTokenAsync(() => RequestToken(clientId, clientSecret));
    }
}
```

### 5. Log Authentication Events

Log authentication events for debugging (but never log secrets):

```csharp
_logger.LogInformation(
    "Token obtained. ClientId: {ClientId}, ExpiresIn: {ExpiresIn}",
    clientId,
    response.ExpiresIn);

// Never log this:
// _logger.LogInformation("Secret: {Secret}", clientSecret); // DON'T DO THIS
```

***

## Error Handling

### Token Request Errors

| Error                    | Cause                              | Solution                 |
| ------------------------ | ---------------------------------- | ------------------------ |
| `invalid_client`         | Wrong client\_id or client\_secret | Verify credentials       |
| `invalid_request`        | Missing required parameters        | Check request format     |
| `unsupported_grant_type` | Wrong grant\_type value            | Use `client_credentials` |

### API Authentication Errors

| Status             | Meaning                          | Action                  |
| ------------------ | -------------------------------- | ----------------------- |
| `401 Unauthorized` | Token invalid or expired         | Refresh token and retry |
| `403 Forbidden`    | Token valid but lacks permission | Check permissions       |

### Retry Logic

Implement exponential backoff for transient errors:

```csharp
public async Task<string> GetTokenWithRetry(int maxRetries = 3)
{
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            return await RequestToken();
        }
        catch (HttpRequestException ex) when (attempt < maxRetries - 1)
        {
            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
            _logger.LogWarning(ex,
                "Token request failed, retrying in {Delay}s",
                delay.TotalSeconds);
            await Task.Delay(delay);
        }
    }

    throw new AuthenticationException("Failed to obtain token after retries");
}
```

***

## Troubleshooting

### "invalid\_client" Error

1. **Verify client\_id** - Check for typos or extra whitespace
2. **Verify client\_secret** - Ensure you're using the correct secret
3. **Check environment** - Make sure you're using the right credentials for the environment (prod vs QA)

### Token Works in Postman but Not in Code

1. **Check Content-Type** - Must be `application/x-www-form-urlencoded`
2. **Verify encoding** - Special characters in credentials must be URL-encoded
3. **Check for whitespace** - Trim any leading/trailing whitespace from credentials

### 401 Errors After Token Refresh

1. **Check clock sync** - Ensure your server's clock is synchronized (NTP)
2. **Verify token** - Decode the JWT at [jwt.io](https://jwt.io) to check claims
3. **Check expiration** - The `exp` claim should be in the future

### Intermittent Authentication Failures

1. **Implement retry logic** - Network issues can cause transient failures
2. **Check for race conditions** - Ensure token refresh is thread-safe
3. **Monitor token expiry** - Refresh tokens proactively, not reactively

***

## Complete Example

A production-ready token management implementation:

```csharp
public interface ITokenService
{
    Task<string> GetAccessTokenAsync();
}

public class TokenService : ITokenService, IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<TokenService> _logger;
    private readonly string _tokenEndpoint;
    private readonly string _clientId;
    private readonly string _clientSecret;

    private string _accessToken;
    private DateTime _tokenExpiry;
    private readonly SemaphoreSlim _refreshLock = new(1, 1);

    public TokenService(
        IHttpClientFactory httpClientFactory,
        ILogger<TokenService> logger,
        IConfiguration configuration)
    {
        _httpClient = httpClientFactory.CreateClient();
        _logger = logger;
        _tokenEndpoint = configuration["PartsSource:TokenEndpoint"];
        _clientId = configuration["PartsSource:ClientId"];
        _clientSecret = configuration["PartsSource:ClientSecret"];
    }

    public async Task<string> GetAccessTokenAsync()
    {
        // Return cached token if valid
        if (IsTokenValid())
        {
            return _accessToken;
        }

        await _refreshLock.WaitAsync();
        try
        {
            // Double-check after acquiring lock
            if (IsTokenValid())
            {
                return _accessToken;
            }

            await RefreshTokenAsync();
            return _accessToken;
        }
        finally
        {
            _refreshLock.Release();
        }
    }

    private bool IsTokenValid()
    {
        return !string.IsNullOrEmpty(_accessToken)
            && DateTime.UtcNow < _tokenExpiry.AddMinutes(-5);
    }

    private async Task RefreshTokenAsync()
    {
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "client_credentials"),
            new KeyValuePair<string, string>("client_id", _clientId),
            new KeyValuePair<string, string>("client_secret", _clientSecret)
        });

        var response = await _httpClient.PostAsync(_tokenEndpoint, content);

        if (!response.IsSuccessStatusCode)
        {
            var error = await response.Content.ReadAsStringAsync();
            _logger.LogError("Token request failed: {Error}", error);
            throw new AuthenticationException($"Failed to obtain token: {error}");
        }

        var tokenResponse = await response.Content
            .ReadFromJsonAsync<TokenResponse>();

        _accessToken = tokenResponse.AccessToken;
        _tokenExpiry = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn);

        _logger.LogInformation(
            "Token refreshed. Expires at {Expiry}",
            _tokenExpiry);
    }

    public void Dispose()
    {
        _refreshLock?.Dispose();
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.partssource.com/api/authentication/oauth-2.0-flow/best-practices-and-error-handling.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
