diff --git a/access-token-management/src/AccessTokenManagement.OpenIdConnect/OpenIdConnectTokenManagementDefaults.cs b/access-token-management/src/AccessTokenManagement.OpenIdConnect/OpenIdConnectTokenManagementDefaults.cs index 4b4d9d619..4fc36301d 100644 --- a/access-token-management/src/AccessTokenManagement.OpenIdConnect/OpenIdConnectTokenManagementDefaults.cs +++ b/access-token-management/src/AccessTokenManagement.OpenIdConnect/OpenIdConnectTokenManagementDefaults.cs @@ -13,6 +13,12 @@ public static class OpenIdConnectTokenManagementDefaults /// public const string ClientCredentialsClientNamePrefix = "Duende.TokenManagement.SchemeBasedClient:"; + /// + /// Converts an authentication scheme to the synthetic client-credentials client name + /// used internally for per-scheme token handling. + /// + /// The authentication scheme. + /// The derived client name. public static ClientCredentialsClientName ToClientName(this Scheme scheme) => ClientCredentialsClientName.Parse(ClientCredentialsClientNamePrefix + scheme); } diff --git a/access-token-management/src/AccessTokenManagement.OpenIdConnect/TokenForParameters.cs b/access-token-management/src/AccessTokenManagement.OpenIdConnect/TokenForParameters.cs index d72a9788a..5f23a1f64 100644 --- a/access-token-management/src/AccessTokenManagement.OpenIdConnect/TokenForParameters.cs +++ b/access-token-management/src/AccessTokenManagement.OpenIdConnect/TokenForParameters.cs @@ -52,6 +52,13 @@ public TokenForParameters(UserRefreshToken refreshToken) /// public UserRefreshToken? RefreshToken { get; } + /// + /// Indicates whether no refresh token exists. + /// + /// + /// When , is populated. + /// When , is populated. + /// [MemberNotNullWhen(true, nameof(TokenForSpecifiedParameters))] [MemberNotNullWhen(false, nameof(RefreshToken))] public bool NoRefreshToken { get; private set; } diff --git a/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs b/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs index 35883da8d..969201010 100644 --- a/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs +++ b/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs @@ -17,6 +17,9 @@ public sealed record UserToken : AccessTokenRequestHandler.IToken /// public required AccessToken AccessToken { get; init; } + /// + /// The DPoP JSON Web Key (JWK) associated with this token when the token is DPoP-bound. + /// public DPoPProofKey? DPoPJsonWebKey { get; init; } /// diff --git a/access-token-management/src/AccessTokenManagement/AccessToken.cs b/access-token-management/src/AccessTokenManagement/AccessToken.cs index de34344e7..1f0d8a3f9 100644 --- a/access-token-management/src/AccessTokenManagement/AccessToken.cs +++ b/access-token-management/src/AccessTokenManagement/AccessToken.cs @@ -13,9 +13,15 @@ namespace Duende.AccessTokenManagement; [JsonConverter(typeof(StringValueJsonConverter))] public readonly record struct AccessToken : IStronglyTypedValue { + /// + /// Returns the wrapped access token string. + /// public override string ToString() => Value; - // Officially, there's no max length for JWTs, but 32k is a good limit + /// + /// The maximum supported length for an access token string. + /// + // Officially, there's no max length for JWTs, but 32k is a good limit. public const int MaxLength = 32 * 1024; // 32k private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/AccessTokenRequestHandler.cs b/access-token-management/src/AccessTokenManagement/AccessTokenRequestHandler.cs index 6adef2b52..76787d820 100644 --- a/access-token-management/src/AccessTokenManagement/AccessTokenRequestHandler.cs +++ b/access-token-management/src/AccessTokenManagement/AccessTokenRequestHandler.cs @@ -21,10 +21,12 @@ public sealed class AccessTokenRequestHandler( ILogger logger) : DelegatingHandler { + /// protected override HttpResponseMessage Send(HttpRequestMessage request, CT ct) => throw new NotSupportedException( "The (synchronous) Send() method is not supported. Please use the async SendAsync variant. "); + /// protected override async Task SendAsync(HttpRequestMessage request, CT ct) { @@ -144,6 +146,9 @@ Task> GetTokenAsync( /// public interface IToken { + /// + /// The access token value to send. + /// AccessToken AccessToken { get; } /// @@ -156,6 +161,9 @@ public interface IToken /// ClientId ClientId { get; } + /// + /// The HTTP authorization scheme for the access token. + /// AccessTokenType? AccessTokenType { get; } } } diff --git a/access-token-management/src/AccessTokenManagement/ClientCredentialsCacheKey.cs b/access-token-management/src/AccessTokenManagement/ClientCredentialsCacheKey.cs index 1695c0813..dc38f98de 100644 --- a/access-token-management/src/AccessTokenManagement/ClientCredentialsCacheKey.cs +++ b/access-token-management/src/AccessTokenManagement/ClientCredentialsCacheKey.cs @@ -11,8 +11,14 @@ namespace Duende.AccessTokenManagement; /// public readonly record struct ClientCredentialsCacheKey : IStronglyTypedValue { + /// + /// Returns the wrapped cache key string. + /// public override string ToString() => Value; + /// + /// The maximum supported length for a cache key value. + /// public const int MaxLength = 1024; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/ClientCredentialsClientName.cs b/access-token-management/src/AccessTokenManagement/ClientCredentialsClientName.cs index 325563c13..2c2b410b7 100644 --- a/access-token-management/src/AccessTokenManagement/ClientCredentialsClientName.cs +++ b/access-token-management/src/AccessTokenManagement/ClientCredentialsClientName.cs @@ -22,6 +22,9 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(ClientCredentialsClientName value) => value.ToString(); + /// + /// Returns the wrapped client name string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/ClientCredentialsTokenManagementBuilder.cs b/access-token-management/src/AccessTokenManagement/ClientCredentialsTokenManagementBuilder.cs index 9ad6ab5df..2079fcbb0 100644 --- a/access-token-management/src/AccessTokenManagement/ClientCredentialsTokenManagementBuilder.cs +++ b/access-token-management/src/AccessTokenManagement/ClientCredentialsTokenManagementBuilder.cs @@ -10,14 +10,17 @@ namespace Duende.AccessTokenManagement; /// public sealed class ClientCredentialsTokenManagementBuilder(IServiceCollection services) { + /// + /// The service collection being configured. + /// public IServiceCollection Services { get; } = services; /// /// Adds a client credentials client to the token management system /// - /// - /// - /// + /// The logical name of the client configuration. + /// A delegate that configures the named options. + /// The same builder instance for chaining. public ClientCredentialsTokenManagementBuilder AddClient(string name, Action configureOptions) { diff --git a/access-token-management/src/AccessTokenManagement/ClientId.cs b/access-token-management/src/AccessTokenManagement/ClientId.cs index 6100db516..a361abc06 100644 --- a/access-token-management/src/AccessTokenManagement/ClientId.cs +++ b/access-token-management/src/AccessTokenManagement/ClientId.cs @@ -21,22 +21,40 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(ClientId value) => value.ToString(); + /// + /// Returns the wrapped client identifier string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ ValidationRules.MaxLength(1024) ]; - + /// + /// Prevents creating an uninitialized instance. + /// + /// Always thrown. public ClientId() => throw new InvalidOperationException("Can't create null value"); private ClientId(string value) => Value = value; private string Value { get; } + /// + /// Attempts to parse and validate a client identifier string. + /// + /// The value to parse. + /// The parsed when parsing succeeds. + /// Validation errors when parsing fails. + /// when parsing succeeds; otherwise . public static bool TryParse(string value, [NotNullWhen(true)] out ClientId? parsed, out string[] errors) => IStronglyTypedValue.TryBuildValidatedObject(value, Validators, out parsed, out errors); static ClientId IStronglyTypedValue.Create(string result) => new(result); + /// + /// Parses and validates a client identifier string. + /// + /// The value to parse. + /// The parsed . public static ClientId Parse(string value) => StringParsers.Parse(value); } diff --git a/access-token-management/src/AccessTokenManagement/ClientSecret.cs b/access-token-management/src/AccessTokenManagement/ClientSecret.cs index e01e52c1d..634b144cf 100644 --- a/access-token-management/src/AccessTokenManagement/ClientSecret.cs +++ b/access-token-management/src/AccessTokenManagement/ClientSecret.cs @@ -7,6 +7,9 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents an OAuth/OIDC client secret value. +/// [TypeConverter(typeof(StringValueConverter))] public readonly record struct ClientSecret : IStronglyTypedValue { @@ -16,6 +19,9 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(ClientSecret value) => value.ToString(); + /// + /// Returns the wrapped client secret string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPExtensions.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPExtensions.cs index 2bd3e373b..5c864fef1 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPExtensions.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPExtensions.cs @@ -14,8 +14,18 @@ public static class DPoPExtensions private static readonly HttpRequestOptionsKey ForceRenewalOptionsKey = new("Duende.AccessTokenManagement.ForceRenewal"); private static readonly HttpRequestOptionsKey DPoPNonceOptionsKey = new("Duende.AccessTokenManagement.DPoPNonce"); + /// + /// Sets whether this request should force token renewal. + /// + /// The request to annotate. + /// to force renewal; otherwise . public static void SetForceRenewal(this HttpRequestMessage request, bool forceTokenRenewal) => request.Options.Set(ForceRenewalOptionsKey, forceTokenRenewal); + /// + /// Gets whether this request is marked to force token renewal. + /// + /// The request to inspect. + /// if force-renewal is enabled; otherwise . public static bool GetForceRenewal(this HttpRequestMessage request) { if (request.Options.TryGetValue(ForceRenewalOptionsKey, out var forceRenewal)) @@ -25,6 +35,11 @@ public static bool GetForceRenewal(this HttpRequestMessage request) return false; } + /// + /// Gets the DPoP nonce associated with this request, if one was set. + /// + /// The request to inspect. + /// The nonce value, or when none is present. public static DPoPNonce? GetDPoPNonce(this HttpRequestMessage request) { if (request.Options.TryGetValue(DPoPNonceOptionsKey, out var nonce)) @@ -33,6 +48,12 @@ public static bool GetForceRenewal(this HttpRequestMessage request) } return null; } + + /// + /// Sets the DPoP nonce to use when generating a proof for this request. + /// + /// The request to annotate. + /// The nonce value. public static void SetDPoPNonce(this HttpRequestMessage request, DPoPNonce nonce) => request.Options.Set(DPoPNonceOptionsKey, nonce); /// diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonce.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonce.cs index a534576ec..0b3170a25 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonce.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonce.cs @@ -15,6 +15,9 @@ namespace Duende.AccessTokenManagement.DPoP; /// public readonly record struct DPoPNonce : IStronglyTypedValue { + /// + /// Returns the wrapped nonce string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = @@ -24,18 +27,40 @@ namespace Duende.AccessTokenManagement.DPoP; ValidationRules.MaxLength(4 * 1024), ]; + /// + /// Prevents creating an uninitialized instance. + /// + /// Always thrown. public DPoPNonce() => throw new InvalidOperationException("Can't create null value"); private DPoPNonce(string value) => Value = value; private string Value { get; } + /// + /// Attempts to parse and validate a nonce string. + /// + /// The nonce string to parse. + /// The parsed when parsing succeeds. + /// Validation errors when parsing fails. + /// when parsing succeeds; otherwise . public static bool TryParse(string value, [NotNullWhen(true)] out DPoPNonce? parsed, out string[] errors) => IStronglyTypedValue.TryBuildValidatedObject(value, Validators, out parsed, out errors); static DPoPNonce IStronglyTypedValue.Create(string result) => new(result); + /// + /// Parses and validates a nonce string. + /// + /// The nonce string to parse. + /// The parsed . public static DPoPNonce Parse(string value) => StringParsers.Parse(value); + + /// + /// Parses and validates a nonce string, returning when the input is null or whitespace. + /// + /// The nonce string to parse. + /// The parsed , or . public static DPoPNonce? ParseOrDefault(string? value) => StringParsers.ParseOrDefault(value); } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonceContext.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonceContext.cs index 508b2b2a9..247f48c3f 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonceContext.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPNonceContext.cs @@ -4,17 +4,17 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// The context for a DPoP nonce. +/// Identifies the request target for storing and retrieving DPoP nonces. /// public sealed record DPoPNonceContext { /// - /// The HTTP URL of the request + /// The HTTP URL of the request. /// public required Uri Url { get; set; } /// - /// The HTTP method of the request + /// The HTTP method of the request. /// public required HttpMethod Method { get; set; } } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProof.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProof.cs index 5abf6de58..41509225e 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProof.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProof.cs @@ -7,8 +7,13 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// Represents a strongly-typed DPoP proof value. +/// Represents a single signed DPoP proof JWT value. /// +/// +/// This value is sent in the HTTP DPoP header for one request. +/// It is generated from a by +/// using a . +/// public readonly record struct DPoPProof : IStronglyTypedValue { /// diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs index d43b5285d..d2ec025de 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs @@ -9,12 +9,28 @@ namespace Duende.AccessTokenManagement.DPoP; +/// +/// Represents the JSON Web Key (JWK) used to sign values. +/// +/// +/// A proof key is the long-lived key material that binds issued tokens to a client. +/// A per-request is generated from this key by . +/// [TypeConverter(typeof(StringValueConverter))] [JsonConverter(typeof(StringValueJsonConverter))] public readonly record struct DPoPProofKey : IStronglyTypedValue { + /// + /// Determines whether this proof key value equals another proof key value. + /// + /// The other value to compare. + /// when both values are equal; otherwise . public bool Equals(DPoPProofKey other) => Value == other.Value; + /// + /// Returns the hash code for the wrapped key string. + /// + /// The hash code. public override int GetHashCode() => Value.GetHashCode(); /// @@ -25,6 +41,9 @@ namespace Duende.AccessTokenManagement.DPoP; private readonly JsonWebKey _jsonWebKey; + /// + /// Returns the wrapped JWK string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ @@ -50,9 +69,9 @@ private static ValidationRule IsValidJsonWebKey() => }; /// - /// You can't directly create this type. + /// Prevents creating an uninitialized instance. /// - /// + /// Always thrown. public DPoPProofKey() => throw new InvalidOperationException("Can't create null value"); private DPoPProofKey(string value) { diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofRequest.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofRequest.cs index c6e330732..2a7e49b56 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofRequest.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofRequest.cs @@ -4,7 +4,7 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// Models a DPoP proof token. +/// Parameters required to create a for an outgoing HTTP request. /// public sealed record DPoPProofRequest { @@ -19,17 +19,17 @@ public sealed record DPoPProofRequest public required HttpMethod Method { get; init; } /// - /// The JSON web key used to sign the DPoP proof. + /// The proof key used to sign the DPoP proof. /// public required DPoPProofKey DPoPProofKey { get; init; } /// - /// The nonce value for the DPoP proof token. + /// The server-provided nonce to include in the generated proof, when required. /// public DPoPNonce? DPoPNonce { get; init; } /// - /// The access token + /// The access token that the proof is bound to, when available. /// public AccessToken? AccessToken { get; init; } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofThumbPrint.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofThumbPrint.cs index 174032f62..b1e3ebcdb 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofThumbPrint.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofThumbPrint.cs @@ -8,10 +8,13 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// Captures a dpop proof thumbprint. +/// Represents the JWK thumbprint for a . /// public readonly record struct DPoPProofThumbprint : IStronglyTypedValue { + /// + /// Returns the wrapped thumbprint string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ @@ -21,15 +24,31 @@ namespace Duende.AccessTokenManagement.DPoP; ValidationRules.MaxLength(255), ]; + /// + /// Prevents creating an uninitialized instance. + /// + /// Always thrown. public DPoPProofThumbprint() => throw new InvalidOperationException("Can't create null value"); private DPoPProofThumbprint(string value) => Value = value; private string Value { get; } + /// + /// Attempts to parse and validate a thumbprint string. + /// + /// The thumbprint string to parse. + /// The parsed value when parsing succeeds. + /// Validation errors when parsing fails. + /// when parsing succeeds; otherwise . public static bool TryParse(string value, [NotNullWhen(true)] out DPoPProofThumbprint? parsed, out string[] errors) => IStronglyTypedValue.TryBuildValidatedObject(value, Validators, out parsed, out errors); + /// + /// Computes a thumbprint from a JSON Web Key. + /// + /// The key to compute the thumbprint from. + /// The computed value. public static DPoPProofThumbprint FromJsonWebKey(JsonWebKey jsonWebKey) { var value = Base64UrlEncoder.Encode(jsonWebKey.ComputeJwkThumbprint()); @@ -38,6 +57,11 @@ public static DPoPProofThumbprint FromJsonWebKey(JsonWebKey jsonWebKey) static DPoPProofThumbprint IStronglyTypedValue.Create(string result) => new(result); + /// + /// Parses and validates a thumbprint string. + /// + /// The thumbprint string to parse. + /// The parsed . public static DPoPProofThumbprint Parse(string value) => StringParsers.Parse(value); } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStore.cs b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStore.cs index eb4818cbc..bb328f71e 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStore.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStore.cs @@ -4,17 +4,24 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// Service to keep track of DPoP nonces +/// Stores and retrieves server-provided DPoP nonces between requests. /// public interface IDPoPNonceStore { /// - /// Gets the nonce + /// Gets the nonce for a DPoP request context. /// + /// The context used to locate the stored nonce. + /// The cancellation token. + /// The nonce, or when none is stored. Task GetNonceAsync(DPoPNonceContext context, CT ct = default); /// - /// Stores the nonce + /// Stores a nonce for a DPoP request context. /// + /// The context used to key the nonce value. + /// The nonce to store. + /// The cancellation token. + /// A task that completes when persistence finishes. Task StoreNonceAsync(DPoPNonceContext context, DPoPNonce nonce, CT ct = default); } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStoreKeyGenerator.cs b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStoreKeyGenerator.cs index 46a7a36d1..9ff88680f 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStoreKeyGenerator.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPNonceStoreKeyGenerator.cs @@ -4,14 +4,14 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// The logic to generate a key to store a DPoP nonce in the Cache +/// Generates stable cache keys for entries. /// public interface IDPoPNonceStoreKeyGenerator { /// - /// Method to generate a cache key for a DPoP nonce + /// Generates a cache key for a DPoP nonce context. /// - /// - /// + /// The request context used to derive the key. + /// A cache key for the nonce store. string GenerateKey(DPoPNonceContext context); } diff --git a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPProofService.cs b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPProofService.cs index 3e56e65c9..9dc130527 100644 --- a/access-token-management/src/AccessTokenManagement/DPoP/IDPoPProofService.cs +++ b/access-token-management/src/AccessTokenManagement/DPoP/IDPoPProofService.cs @@ -4,18 +4,23 @@ namespace Duende.AccessTokenManagement.DPoP; /// -/// Service to create DPoP proof tokens +/// Creates DPoP proofs and related key material metadata. /// public interface IDPoPProofService { /// - /// Serializes a requested model into a . + /// Creates a signed from request parameters. /// + /// The inputs for generating the proof JWT. + /// The cancellation token. + /// The proof token, or when no proof should be sent. Task CreateProofTokenAsync(DPoPProofRequest request, CT ct = default); /// - /// Computes the thumbprint of the JSON web key. + /// Computes the thumbprint of a proof key. /// + /// The proof key to hash. + /// The thumbprint value, or if it cannot be computed. DPoPProofThumbprint? GetProofKeyThumbprint(DPoPProofKey dpopProofKey); } diff --git a/access-token-management/src/AccessTokenManagement/FailedResult.cs b/access-token-management/src/AccessTokenManagement/FailedResult.cs index 3333cd930..3065f9330 100644 --- a/access-token-management/src/AccessTokenManagement/FailedResult.cs +++ b/access-token-management/src/AccessTokenManagement/FailedResult.cs @@ -3,8 +3,16 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents a protocol-level token acquisition failure. +/// +/// The OAuth/OIDC error code. +/// An optional error description from the token endpoint. public sealed record FailedResult(string Error, string? ErrorDescription = null) : TokenResult { + /// + /// Formats the failure details for logging and diagnostics. + /// public override string ToString() { var description = string.IsNullOrEmpty(ErrorDescription) ? string.Empty : $" with description {ErrorDescription}"; diff --git a/access-token-management/src/AccessTokenManagement/ForceTokenRenewal.cs b/access-token-management/src/AccessTokenManagement/ForceTokenRenewal.cs index 81f6a2c0b..6de64a27c 100644 --- a/access-token-management/src/AccessTokenManagement/ForceTokenRenewal.cs +++ b/access-token-management/src/AccessTokenManagement/ForceTokenRenewal.cs @@ -3,4 +3,8 @@ namespace Duende.AccessTokenManagement; +/// +/// Indicates whether token retrieval should bypass normal cache reuse and force renewal. +/// +/// to force token renewal; otherwise . public readonly record struct ForceTokenRenewal(bool Value); diff --git a/access-token-management/src/AccessTokenManagement/HttpRequestContext.cs b/access-token-management/src/AccessTokenManagement/HttpRequestContext.cs index 917933544..2d81d7780 100644 --- a/access-token-management/src/AccessTokenManagement/HttpRequestContext.cs +++ b/access-token-management/src/AccessTokenManagement/HttpRequestContext.cs @@ -8,7 +8,18 @@ namespace Duende.AccessTokenManagement; /// public record struct HttpRequestContext { + /// + /// The HTTP method (for example GET or POST). + /// public required string Method { get; init; } + + /// + /// The request URI. + /// public required Uri? RequestUri { get; init; } + + /// + /// The request headers. + /// public required IEnumerable>> Headers { get; init; } } diff --git a/access-token-management/src/AccessTokenManagement/HybridCacheConstants.cs b/access-token-management/src/AccessTokenManagement/HybridCacheConstants.cs index 39e0270fd..0a9c10b8a 100644 --- a/access-token-management/src/AccessTokenManagement/HybridCacheConstants.cs +++ b/access-token-management/src/AccessTokenManagement/HybridCacheConstants.cs @@ -3,6 +3,9 @@ namespace Duende.AccessTokenManagement; +/// +/// Well-known cache tags used by access token management. +/// public static class HybridCacheConstants { /// diff --git a/access-token-management/src/AccessTokenManagement/IClientCredentialsTokenManager.cs b/access-token-management/src/AccessTokenManagement/IClientCredentialsTokenManager.cs index e5fe70cb5..515fbadc7 100644 --- a/access-token-management/src/AccessTokenManagement/IClientCredentialsTokenManager.cs +++ b/access-token-management/src/AccessTokenManagement/IClientCredentialsTokenManager.cs @@ -3,13 +3,34 @@ namespace Duende.AccessTokenManagement; +/// +/// Provides the main API for acquiring and managing client-credentials access tokens. +/// +/// +/// Implementations are responsible for caching, renewal, and token endpoint interaction +/// for configured clients. +/// public interface IClientCredentialsTokenManager { + /// + /// Gets an access token for the named client. + /// + /// The configured client-credentials client name. + /// Optional request parameters that influence token retrieval and caching. + /// The cancellation token. + /// A containing either a token or protocol failure details. Task> GetAccessTokenAsync( ClientCredentialsClientName clientName, TokenRequestParameters? parameters = null, CT ct = default); + /// + /// Deletes the cached access token for the named client and request parameters. + /// + /// The configured client-credentials client name. + /// Optional request parameters used to identify the cached token entry. + /// The cancellation token. + /// A task that completes when deletion has finished. Task DeleteAccessTokenAsync(ClientCredentialsClientName clientName, TokenRequestParameters? parameters = null, CT ct = default); diff --git a/access-token-management/src/AccessTokenManagement/IdentityToken.cs b/access-token-management/src/AccessTokenManagement/IdentityToken.cs index d8b00cd48..05780dd0b 100644 --- a/access-token-management/src/AccessTokenManagement/IdentityToken.cs +++ b/access-token-management/src/AccessTokenManagement/IdentityToken.cs @@ -7,10 +7,20 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents an OpenID Connect identity token (ID token). +/// [JsonConverter(typeof(StringValueJsonConverter))] public readonly record struct IdentityToken : IStronglyTypedValue { + /// + /// The maximum supported length for an identity token string. + /// public const int MaxLength = 32 * 1024; + + /// + /// Returns the wrapped identity token string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/OTel/AccessTokenManagementMetrics.cs b/access-token-management/src/AccessTokenManagement/OTel/AccessTokenManagementMetrics.cs index fedd88bc2..41e7a3670 100644 --- a/access-token-management/src/AccessTokenManagement/OTel/AccessTokenManagementMetrics.cs +++ b/access-token-management/src/AccessTokenManagement/OTel/AccessTokenManagementMetrics.cs @@ -7,8 +7,14 @@ namespace Duende.AccessTokenManagement.OTel; +/// +/// Emits OpenTelemetry metrics for access token management operations. +/// public sealed class AccessTokenManagementMetrics { + /// + /// The meter name used by this library. + /// public const string MeterName = "Duende.AccessTokenManagement"; private readonly Counter _accessTokenUsed; @@ -17,6 +23,10 @@ public sealed class AccessTokenManagementMetrics private readonly Counter _accessTokenAccessDeniedRetry; private readonly Counter _dpopNonceErrorRetry; + /// + /// Creates a new metrics publisher. + /// + /// The meter factory used to create counters. public AccessTokenManagementMetrics(IMeterFactory meterFactory) { var meter = meterFactory.Create(MeterName); @@ -48,7 +58,14 @@ public AccessTokenManagementMetrics(IMeterFactory meterFactory) /// public enum TokenRequestType { + /// + /// A client-credentials token request. + /// ClientCredentials = 1, + + /// + /// A user token request. + /// User = 2 } @@ -122,6 +139,11 @@ public void AccessTokenAccessDeniedRetry(ClientId? clientId) ); } + /// + /// Writes a metric when an operation retries after a DPoP nonce-related error. + /// + /// The client identifier associated with the request. + /// The reported DPoP error value. public void DPoPNonceErrorRetry(ClientId? clientId, string? error) { if (!_dpopNonceErrorRetry.Enabled) @@ -135,4 +157,3 @@ public void DPoPNonceErrorRetry(ClientId? clientId, string? error) } } - diff --git a/access-token-management/src/AccessTokenManagement/OTel/ActivitySources.cs b/access-token-management/src/AccessTokenManagement/OTel/ActivitySources.cs index fc05dc4bd..b6d33be08 100644 --- a/access-token-management/src/AccessTokenManagement/OTel/ActivitySources.cs +++ b/access-token-management/src/AccessTokenManagement/OTel/ActivitySources.cs @@ -5,18 +5,36 @@ namespace Duende.AccessTokenManagement.OTel; +/// +/// Exposes activity sources used by access token management diagnostics. +/// public static class ActivitySources { + /// + /// The primary activity source for token management operations. + /// public static ActivitySource Main = new(ActivitySourceNames.Main); } +/// +/// Activity source names used by access token management diagnostics. +/// public static class ActivitySourceNames { + /// + /// The name used for . + /// public static readonly string Main = typeof(ActivitySources).Assembly.GetName().Name!; } +/// +/// Activity names emitted by access token management instrumentation. +/// public static class ActivityNames { + /// + /// Activity name for token acquisition operations. + /// public const string AcquiringToken = "Duende.AccessTokenManagement.AcquiringToken"; } diff --git a/access-token-management/src/AccessTokenManagement/RefreshToken.cs b/access-token-management/src/AccessTokenManagement/RefreshToken.cs index d0d49a234..669febc04 100644 --- a/access-token-management/src/AccessTokenManagement/RefreshToken.cs +++ b/access-token-management/src/AccessTokenManagement/RefreshToken.cs @@ -7,10 +7,20 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents an OAuth refresh token value. +/// [JsonConverter(typeof(StringValueJsonConverter))] public readonly record struct RefreshToken : IStronglyTypedValue { + /// + /// The maximum supported length for a refresh token string. + /// public const int MaxLength = 4 * 1024; + + /// + /// Returns the wrapped refresh token string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/Resource.cs b/access-token-management/src/AccessTokenManagement/Resource.cs index f92c2e174..41a68f0f6 100644 --- a/access-token-management/src/AccessTokenManagement/Resource.cs +++ b/access-token-management/src/AccessTokenManagement/Resource.cs @@ -7,9 +7,15 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents an OAuth resource parameter value. +/// [TypeConverter(typeof(StringValueConverter))] public readonly record struct Resource : IStronglyTypedValue { + /// + /// The maximum supported length for a resource string. + /// public const int MaxLength = 1024; /// @@ -18,6 +24,9 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(Resource value) => value.ToString(); + /// + /// Returns the wrapped resource string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ diff --git a/access-token-management/src/AccessTokenManagement/Scheme.cs b/access-token-management/src/AccessTokenManagement/Scheme.cs index 5e1628f73..9749d71a9 100644 --- a/access-token-management/src/AccessTokenManagement/Scheme.cs +++ b/access-token-management/src/AccessTokenManagement/Scheme.cs @@ -16,6 +16,9 @@ namespace Duende.AccessTokenManagement; [TypeConverter(typeof(StringValueConverter))] public readonly record struct Scheme : IStronglyTypedValue { + /// + /// The maximum supported length for an authorization scheme value. + /// public const int MaxLength = 50; /// @@ -24,12 +27,18 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(Scheme value) => value.ToString(); + /// + /// Returns the wrapped authorization scheme string. + /// public override string ToString() => Value; private static readonly ValidationRule[] Validators = [ ValidationRules.MaxLength(MaxLength), ]; + /// + /// The standard bearer authorization scheme. + /// public static readonly Scheme Bearer = Parse(OidcConstants.AuthenticationSchemes.AuthorizationHeaderBearer); diff --git a/access-token-management/src/AccessTokenManagement/Scope.cs b/access-token-management/src/AccessTokenManagement/Scope.cs index 147465cfe..ff8170f29 100644 --- a/access-token-management/src/AccessTokenManagement/Scope.cs +++ b/access-token-management/src/AccessTokenManagement/Scope.cs @@ -9,10 +9,16 @@ namespace Duende.AccessTokenManagement; +/// +/// Represents one or more OAuth scope tokens as a single space-separated string. +/// [TypeConverter(typeof(StringValueConverter))] [JsonConverter(typeof(StringValueJsonConverter))] public readonly partial record struct Scope : IStronglyTypedValue { + /// + /// The maximum supported length for a scope string. + /// public const int MaxLength = 1024; /// @@ -21,6 +27,9 @@ namespace Duende.AccessTokenManagement; /// public static implicit operator string(Scope value) => value.ToString(); + /// + /// Returns the wrapped scope string. + /// public override string ToString() => Value; // According to RFC 6749, the scope is a space-separated list of scope-token(s). diff --git a/access-token-management/src/AccessTokenManagement/ServiceCollectionExtensions.cs b/access-token-management/src/AccessTokenManagement/ServiceCollectionExtensions.cs index ac2c09c27..55efb4fce 100644 --- a/access-token-management/src/AccessTokenManagement/ServiceCollectionExtensions.cs +++ b/access-token-management/src/AccessTokenManagement/ServiceCollectionExtensions.cs @@ -121,6 +121,11 @@ public static IHttpClientBuilder AddClientCredentialsHttpClient( .AddDefaultAccessTokenResiliency() .AddClientCredentialsTokenHandler(clientName); + /// + /// Adds the default resilience pipeline used by access token management HTTP clients. + /// + /// The HTTP client builder to configure. + /// The same builder instance for chaining. public static IHttpClientBuilder AddDefaultAccessTokenResiliency(this IHttpClientBuilder httpClientBuilder) { httpClientBuilder.AddResilienceHandler("Duende", diff --git a/access-token-management/src/AccessTokenManagement/TokenResult.cs b/access-token-management/src/AccessTokenManagement/TokenResult.cs index 98f09815c..b45295175 100644 --- a/access-token-management/src/AccessTokenManagement/TokenResult.cs +++ b/access-token-management/src/AccessTokenManagement/TokenResult.cs @@ -5,11 +5,26 @@ namespace Duende.AccessTokenManagement; +/// +/// Base type for token acquisition results. +/// public abstract record TokenResult { + /// + /// Creates a failed token result. + /// + /// The OAuth/OIDC error code. + /// An optional error description. + /// A failure result. public static FailedResult Failure(string error, string? errorDescription = null) => new(error, errorDescription); + /// + /// Creates a successful token result. + /// + /// The token type. + /// The token value. + /// A success result. public static TokenResult Success(T token) where T : class => token; } @@ -20,7 +35,7 @@ public static TokenResult Success(T token) where T : class /// are caught and translated to a failure. For example, if the token endpoint is not reachable, /// or if you've misconfigured the library, you may still get an exception. /// -/// +/// The token type. public sealed record TokenResult : TokenResult where T : class { @@ -28,19 +43,40 @@ public sealed record TokenResult : TokenResult private TokenResult(FailedResult failure) => FailedResult = failure; - + /// + /// Indicates whether the token request succeeded. + /// [MemberNotNullWhen(true, nameof(Token))] [MemberNotNullWhen(false, nameof(FailedResult))] public bool Succeeded => FailedResult == null; + /// + /// The failure details when is . + /// public FailedResult? FailedResult { get; } + /// + /// The token value when is . + /// public T? Token { get; } + /// + /// Converts a token into a successful . + /// + /// The token value. public static implicit operator TokenResult(T input) => new(input); + /// + /// Converts a failure into a . + /// + /// The failure details. public static implicit operator TokenResult(FailedResult failure) => new(failure); + /// + /// Determines whether this result succeeded and returns the token when it did. + /// + /// The token result when successful. + /// when successful; otherwise . public bool WasSuccessful(out T result) { if (Succeeded) @@ -53,6 +89,12 @@ public bool WasSuccessful(out T result) return false; } + /// + /// Determines whether this result succeeded and returns either token or failure details. + /// + /// The token result when successful; otherwise . + /// The failure result when unsuccessful; otherwise . + /// when successful; otherwise . public bool WasSuccessful([NotNullWhen(true)] out T? result, [NotNullWhen(false)] out FailedResult? failure) { if (Succeeded) diff --git a/access-token-management/src/AccessTokenManagement/TokenResultExtensions.cs b/access-token-management/src/AccessTokenManagement/TokenResultExtensions.cs index 07cf858e4..6c95d02f4 100644 --- a/access-token-management/src/AccessTokenManagement/TokenResultExtensions.cs +++ b/access-token-management/src/AccessTokenManagement/TokenResultExtensions.cs @@ -3,6 +3,9 @@ namespace Duende.AccessTokenManagement; +/// +/// Extension methods for working with asynchronous values. +/// public static class TokenResultExtensions { /// diff --git a/access-token-management/src/Directory.Build.props b/access-token-management/src/Directory.Build.props index d140eb29b..9cf6cefb2 100644 --- a/access-token-management/src/Directory.Build.props +++ b/access-token-management/src/Directory.Build.props @@ -4,6 +4,7 @@ + $([System.String]::Copy('$(NoWarn)').Replace(';CS1591', '')) atm- 3.0 OAuth 2.0;OpenID Connect;Security;BFF;IdentityServer;ASP.NET Core;SPA;Blazor;Token Management