AuthScape

Docs

Client Credentials

Service-to-service authentication with Client Credentials flow.

Client Credentials Flow

The Client Credentials flow is used for service-to-service authentication where no user is involved.

Use Cases

  • Background services calling APIs
  • Microservice communication
  • Scheduled jobs
  • Server-side integrations

Register Service Client

csharp
public async Task SeedServiceClient()
{
var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
if (await manager.FindByClientIdAsync("background-service") == null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "background-service",
ClientSecret = "service-secret-here",
DisplayName = "Background Service",
Permissions =
{
Permissions.Endpoints.Token,
Permissions.GrantTypes.ClientCredentials,
Permissions.Prefixes.Scope + "api1"
}
});
}
}

Request Access Token

http
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=background-service
&client_secret=service-secret-here
&scope=api1

Response:

json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}

C# Implementation

csharp
public class ServiceAuthenticator
{
private readonly HttpClient _httpClient;
private readonly string _tokenEndpoint;
private readonly string _clientId;
private readonly string _clientSecret;
private string? _accessToken;
private DateTime _tokenExpiry;
public async Task<string> GetAccessTokenAsync()
{
if (_accessToken != null && DateTime.UtcNow < _tokenExpiry)
{
return _accessToken;
}
var request = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _clientId,
["client_secret"] = _clientSecret,
["scope"] = "api1"
})
};
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
var token = JsonSerializer.Deserialize<TokenResponse>(content);
_accessToken = token.AccessToken;
_tokenExpiry = DateTime.UtcNow.AddSeconds(token.ExpiresIn - 60);
return _accessToken;
}
}

Using the Token

csharp
public class ApiClient
{
private readonly HttpClient _httpClient;
private readonly ServiceAuthenticator _authenticator;
public async Task<T> GetAsync<T>(string endpoint)
{
var token = await _authenticator.GetAccessTokenAsync();
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(content);
}
}

Background Service Example

csharp
public class DataSyncService : BackgroundService
{
private readonly ApiClient _apiClient;
private readonly ILogger<DataSyncService> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var data = await _apiClient.GetAsync<SyncData>("/api/sync/pending");
await ProcessData(data);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync failed");
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}

Security Considerations

  1. Secure secret storage - Use Azure Key Vault or AWS Secrets Manager
  2. Rotate secrets regularly - Implement secret rotation
  3. Minimal scopes - Only request necessary permissions
  4. Network isolation - Restrict service communication to internal networks

Next Steps

  • Claims Structure
  • Protecting Endpoints