Appearance
OIDC Client Library
Introduction
The ProAuth OIDC Client library (ProAuth.Oidc.Client) provides a comprehensive set of tools for integrating .NET applications with ProAuth as an OpenID Connect identity provider. It handles OAuth 2.0 / OIDC protocol operations, token management, and secure API communication.
While the BFF package uses this library internally for browser-based applications, you can also use the OIDC Client directly for:
- Backend services requiring service-to-service authentication
- API gateways that need to manage tokens
- Custom authentication flows
- Token exchange scenarios
- Applications requiring direct OIDC protocol access
Features
| Feature | Description |
|---|---|
| Token Management | Automatic token acquisition, caching, and refresh |
| Client Credentials | Service-to-service authentication |
| Token Exchange | RFC 8693 token exchange support |
| Token Introspection | Validate tokens via introspection endpoint |
| Token Revocation | Revoke access and refresh tokens |
| Discovery | Automatic OIDC discovery document handling |
| Distributed Storage | Pluggable token stores (In-Memory, Redis, Dapr, ReaFx) |
| TLS Configuration | Custom CA and certificate trust configuration |
Getting Started
Package Installation
bash
dotnet add package ProAuth.Oidc.Client
# Add a token store provider
dotnet add package ProAuth.Oidc.Client.InMemory # Development
dotnet add package ProAuth.Oidc.Client.Redis # ProductionBasic Setup
csharp
using ProAuth.Oidc.Client;
using ProAuth.Oidc.Client.InMemory;
var builder = WebApplication.CreateBuilder(args);
// Configure authentication settings
builder.Services.Configure<AuthenticationSettings>(
builder.Configuration.GetSection("Authentication"));
// Add OIDC client services
builder.Services.AddOidcClient();
// Add token store and locking
builder.Services.AddSingleton<ITokenStore, InMemoryTokenStore>();
builder.Services.AddSingleton<ILockProvider, InProcessLockProvider>();
// Add HTTP client factory
builder.Services.AddHttpClient();
var app = builder.Build();Configuration
json
{
"Authentication": {
"Authority": "https://auth.example.com",
"ClientId": "my-service",
"ClientSecret": "service-secret",
"ServiceScopes": "openid api://my-api/.default",
"ServiceResources": "api://my-api"
}
}Configuration Options
| Setting | Type | Description |
|---|---|---|
Authority | string | ProAuth server URL |
ClientId | string | OAuth 2.0 client identifier |
ClientSecret | string | OAuth 2.0 client secret |
ServiceScopes | string | Space-separated scopes for client credentials flow |
ServiceResources | string | Space-separated resource identifiers |
UserScopes | string | Scopes for user authentication flows |
UserResources | string | Resources for user authentication flows |
Core Interfaces
IOidcClient
The IOidcClient interface provides direct access to OIDC protocol operations:
csharp
public interface IOidcClient
{
// Client credentials grant
Task<TokenResponse> ClientCredentialsAsync(
IEnumerable<string> scopes = null,
IEnumerable<string> resources = null,
CancellationToken cancellationToken = default);
// Refresh an access token
Task<TokenResponse> RefreshAccessTokenAsync(
string refreshToken,
IEnumerable<string> scopes = null,
IEnumerable<string> resources = null,
CancellationToken cancellationToken = default);
// Token exchange (RFC 8693)
Task<TokenResponse> ExchangeTokenAsync(
string subjectToken,
string subjectTokenType,
string requestedTokenType = null,
IEnumerable<string> scopes = null,
IEnumerable<string> resources = null,
string audience = null,
CancellationToken cancellationToken = default);
// Token introspection
Task<IntrospectionResponse> IntrospectTokenAsync(
string token,
string tokenTypeHint = null,
CancellationToken cancellationToken = default);
// Token revocation
Task<RevokeResponse> RevokeTokenAsync(
string token,
string tokenTypeHint = null,
CancellationToken cancellationToken = default);
}ITokenHandler
The ITokenHandler interface provides high-level token management with automatic caching and refresh:
csharp
public interface ITokenHandler
{
// Get access token for a user (from token store)
Task<JwtSecurityToken> GetAccessTokenForUser(
string subjectIdentifier,
CancellationToken cancellationToken = default);
// Get access token for service-to-service calls
Task<JwtSecurityToken> GetAccessTokenForService(
CancellationToken cancellationToken = default);
}ITokenStore
The ITokenStore interface defines token persistence:
csharp
public interface ITokenStore
{
Task<UserTokens?> GetUserTokens(string subjectIdentifier, CancellationToken ct = default);
Task<ServiceTokens?> GetServiceTokens(string subjectIdentifier, CancellationToken ct = default);
Task StoreUserTokens(string subjectIdentifier, UserTokens userTokens, CancellationToken ct = default);
Task StoreServiceTokens(string subjectIdentifier, ServiceTokens serviceTokens, CancellationToken ct = default);
}Usage Examples
Client Credentials Flow
For service-to-service authentication:
csharp
public class MyBackendService
{
private readonly ITokenHandler _tokenHandler;
private readonly HttpClient _httpClient;
public MyBackendService(ITokenHandler tokenHandler, IHttpClientFactory httpClientFactory)
{
_tokenHandler = tokenHandler;
_httpClient = httpClientFactory.CreateClient();
}
public async Task<string> CallProtectedApiAsync()
{
// Get service access token (automatically cached and refreshed)
var token = await _tokenHandler.GetAccessTokenForService();
// Call protected API
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token.RawData);
var response = await _httpClient.GetAsync("https://api.example.com/data");
return await response.Content.ReadAsStringAsync();
}
}Token Exchange
Exchange a user's token for a token with different scope/audience:
csharp
public class TokenExchangeService
{
private readonly IOidcClient _oidcClient;
public TokenExchangeService(IOidcClient oidcClient)
{
_oidcClient = oidcClient;
}
public async Task<string> ExchangeForDownstreamApiAsync(string userAccessToken)
{
var response = await _oidcClient.ExchangeTokenAsync(
subjectToken: userAccessToken,
subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
scopes: new[] { "api://downstream-api/.default" },
audience: "api://downstream-api");
if (response.IsError)
{
throw new InvalidOperationException($"Token exchange failed: {response.Error}");
}
return response.AccessToken;
}
}Token Introspection
Validate a token and get its claims:
csharp
public class TokenValidationService
{
private readonly IOidcClient _oidcClient;
public TokenValidationService(IOidcClient oidcClient)
{
_oidcClient = oidcClient;
}
public async Task<bool> ValidateTokenAsync(string token)
{
var response = await _oidcClient.IntrospectTokenAsync(
token: token,
tokenTypeHint: "access_token");
if (response.IsError)
{
return false;
}
return response.IsActive;
}
}Token Revocation
Revoke tokens when a user logs out:
csharp
public class LogoutService
{
private readonly IOidcClient _oidcClient;
public LogoutService(IOidcClient oidcClient)
{
_oidcClient = oidcClient;
}
public async Task RevokeUserTokensAsync(string accessToken, string refreshToken)
{
// Revoke refresh token first
if (!string.IsNullOrEmpty(refreshToken))
{
await _oidcClient.RevokeTokenAsync(refreshToken, "refresh_token");
}
// Revoke access token
if (!string.IsNullOrEmpty(accessToken))
{
await _oidcClient.RevokeTokenAsync(accessToken, "access_token");
}
}
}Token Store Providers
In-Memory Store
Package: ProAuth.Oidc.Client.InMemory
csharp
services.AddSingleton<ITokenStore, InMemoryTokenStore>();
services.AddSingleton<ILockProvider, InProcessLockProvider>();Features:
- Automatic cleanup of expired tokens
- Configurable sliding expiration
- Thread-safe concurrent access
Configuration:
csharp
services.Configure<InMemoryTokenStoreOptions>(options =>
{
options.CleanupIntervalMinutes = 5;
options.TokenSlidingExpirationMinutes = 60;
});Redis Store
Package: ProAuth.Oidc.Client.Redis
csharp
services.AddSingleton<ITokenStore, RedisTokenStore>();
services.AddSingleton<ILockProvider, RedisLockProvider>();Configuration:
json
{
"Redis": {
"ConnectionString": "localhost:6379,password=secret,ssl=true"
}
}Dapr Store
Package: ProAuth.Oidc.Client.Dapr
csharp
services.AddSingleton<ITokenStore, DaprTokenStore>();
services.AddSingleton<ILockProvider, DaprLockProvider>();Requires Dapr state store component.
ReaFx Store
Package: ProAuth.Oidc.Client.ReaFx
csharp
services.AddSingleton<ITokenStore, ReaFxTokenStore>();
services.AddSingleton<ILockProvider, ReaFxLockProvider>();INFO
ReaFx integration requires a ReaFx license.
TLS Configuration
For environments with custom CAs or self-signed certificates:
csharp
services.Configure<TlsCertificateValidationConfiguration>(options =>
{
// Trust specific CA certificates
options.CustomTrustedRootCaFilePaths = new[]
{
"/etc/ssl/certs/custom-ca.crt"
};
// Or trust specific server certificates
options.CustomTrustedTlsCertificatePaths = new[]
{
"/etc/ssl/certs/auth-server.crt"
};
// Enable hot-reload of certificates
options.EnableFileSystemWatcher = true;
});DANGER
Never use AcceptAnyServerCertificates = true in production. This disables all certificate validation and exposes your application to man-in-the-middle attacks.
Error Handling
All protocol operations return response objects with error information:
csharp
var response = await _oidcClient.ClientCredentialsAsync();
if (response.IsError)
{
_logger.LogError(
"Token acquisition failed: {Error} - {Description}",
response.Error,
response.ErrorDescription);
throw new AuthenticationException(response.Error);
}
// Use response.AccessTokenThread Safety and Concurrency
The OIDC Client library is designed for concurrent use:
- Token caching: Tokens are cached and only refreshed when necessary
- Distributed locking: Token refresh operations use distributed locks to prevent race conditions
- Thread-safe stores: All token store implementations are thread-safe
Best Practices
Token Refresh Strategy
The ITokenHandler automatically refreshes tokens 30 seconds before expiration. For long-running operations, consider:
csharp
// Check if token will expire soon
var token = await _tokenHandler.GetAccessTokenForService();
if (token.ValidTo < DateTime.UtcNow.AddMinutes(5))
{
// Token is close to expiration, get a fresh one
token = await _tokenHandler.GetAccessTokenForService();
}Connection Resilience
Configure HTTP clients with retry policies:
csharp
services.AddHttpClient("OidcClient")
.AddPolicyHandler(GetRetryPolicy());
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}Secure Secret Storage
Never store client secrets in source code:
csharp
// Use environment variables or secret management
var clientSecret = Environment.GetEnvironmentVariable("OIDC_CLIENT_SECRET")
?? throw new InvalidOperationException("Client secret not configured");See Also
- BFF (Backend-For-Frontend) - For browser-based applications
- Client Applications - Configure clients in ProAuth
- Token Exchange Configuration - Enable token exchange