From edbc746c5357a1681001f267c4858bff4359c472 Mon Sep 17 00:00:00 2001
From: Damian Hickey <57436+damianh@users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:04:21 +0200
Subject: [PATCH 1/2] docs: add xmldoc for ATM public API
Add XML documentation across public ATM and ATM.OpenIdConnect types and members, including DPoP concept clarifications and IClientCredentialsTokenManager docs.
Re-enable CS1591 enforcement for ATM src by removing the inherited suppression in access-token-management/src/Directory.Build.props.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
.../OpenIdConnectTokenManagementDefaults.cs | 6 +++
.../TokenForParameters.cs | 7 +++
.../UserToken.cs | 3 ++
.../src/AccessTokenManagement/AccessToken.cs | 8 +++-
.../AccessTokenRequestHandler.cs | 8 ++++
.../ClientCredentialsCacheKey.cs | 6 +++
.../ClientCredentialsClientName.cs | 3 ++
...ClientCredentialsTokenManagementBuilder.cs | 9 ++--
.../src/AccessTokenManagement/ClientId.cs | 20 +++++++-
.../src/AccessTokenManagement/ClientSecret.cs | 6 +++
.../DPoP/DPoPExtensions.cs | 21 +++++++++
.../AccessTokenManagement/DPoP/DPoPNonce.cs | 25 ++++++++++
.../DPoP/DPoPNonceContext.cs | 6 +--
.../AccessTokenManagement/DPoP/DPoPProof.cs | 7 ++-
.../DPoP/DPoPProofKey.cs | 23 ++++++++++
.../DPoP/DPoPProofRequest.cs | 8 ++--
.../DPoP/DPoPProofThumbPrint.cs | 26 ++++++++++-
.../DPoP/IDPoPNonceStore.cs | 13 ++++--
.../DPoP/IDPoPNonceStoreKeyGenerator.cs | 8 ++--
.../DPoP/IDPoPProofService.cs | 11 +++--
.../src/AccessTokenManagement/FailedResult.cs | 8 ++++
.../ForceTokenRenewal.cs | 4 ++
.../HttpRequestContext.cs | 11 +++++
.../HybridCacheConstants.cs | 3 ++
.../IClientCredentialsTokenManager.cs | 21 +++++++++
.../AccessTokenManagement/IdentityToken.cs | 10 ++++
.../OTel/AccessTokenManagementMetrics.cs | 23 +++++++++-
.../OTel/ActivitySources.cs | 18 ++++++++
.../src/AccessTokenManagement/RefreshToken.cs | 10 ++++
.../src/AccessTokenManagement/Resource.cs | 9 ++++
.../src/AccessTokenManagement/Scheme.cs | 9 ++++
.../src/AccessTokenManagement/Scope.cs | 9 ++++
.../ServiceCollectionExtensions.cs | 5 ++
.../src/AccessTokenManagement/TokenResult.cs | 46 ++++++++++++++++++-
.../TokenResultExtensions.cs | 3 ++
.../src/Directory.Build.props | 1 +
36 files changed, 387 insertions(+), 27 deletions(-)
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..d297e79db 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 proof key 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..381a2f8ca 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 = [
@@ -53,6 +72,10 @@ 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..086d665bd 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
From 4a49845346e6bed688c10ffca76b47fbcd6f6baf Mon Sep 17 00:00:00 2001
From: Damian Hickey <57436+damianh@users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:21:49 +0200
Subject: [PATCH 2/2] docs: address PR review comments
Fix malformed XML doc block in DPoPProofKey ctor docs, tighten CS1591 removal to ';CS1591' in ATM Directory.Build.props, and align UserToken DPoPJsonWebKey docs with JWK terminology.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
.../src/AccessTokenManagement.OpenIdConnect/UserToken.cs | 2 +-
.../src/AccessTokenManagement/DPoP/DPoPProofKey.cs | 4 ----
access-token-management/src/Directory.Build.props | 2 +-
3 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs b/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs
index d297e79db..969201010 100644
--- a/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs
+++ b/access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs
@@ -18,7 +18,7 @@ public sealed record UserToken : AccessTokenRequestHandler.IToken
public required AccessToken AccessToken { get; init; }
///
- /// The DPoP proof key associated with this token when the token is DPoP-bound.
+ /// 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/DPoP/DPoPProofKey.cs b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs
index 381a2f8ca..d2ec025de 100644
--- a/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs
+++ b/access-token-management/src/AccessTokenManagement/DPoP/DPoPProofKey.cs
@@ -68,10 +68,6 @@ private static ValidationRule IsValidJsonWebKey() =>
}
};
- ///
- /// You can't directly create this type.
- ///
- ///
///
/// Prevents creating an uninitialized instance.
///
diff --git a/access-token-management/src/Directory.Build.props b/access-token-management/src/Directory.Build.props
index 086d665bd..9cf6cefb2 100644
--- a/access-token-management/src/Directory.Build.props
+++ b/access-token-management/src/Directory.Build.props
@@ -4,7 +4,7 @@
- $([System.String]::Copy('$(NoWarn)').Replace('CS1591', ''))
+ $([System.String]::Copy('$(NoWarn)').Replace(';CS1591', ''))
atm-
3.0
OAuth 2.0;OpenID Connect;Security;BFF;IdentityServer;ASP.NET Core;SPA;Blazor;Token Management