Skip to content

Commit 76338ef

Browse files
Made some changes to the strongly typed clients
1 parent 90de031 commit 76338ef

File tree

74 files changed

+479
-336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+479
-336
lines changed

access-token-management/samples/Web/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
7979
builder.Services.AddOpenIdConnectAccessTokenManagement(options =>
8080
{
8181
var useDPoP = builder.Configuration.GetValue<bool>("UseDPoP");
82-
options.DPoPJsonWebKey = useDPoP ? ProofKeyString.ParseOrDefault(jwk) : null;
82+
options.DPoPJsonWebKey = useDPoP ? DPoPProofKey.ParseOrDefault(jwk) : null;
8383
});
8484

8585
// registers HTTP client that uses the managed user access token

access-token-management/samples/WebJarJwt/ClientAssertionService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class ClientAssertionService : IClientAssertionService
3737
public ClientAssertionService(
3838
IOpenIdConnectConfigurationService configurationService) => _configurationService = configurationService;
3939

40-
public async Task<ClientAssertion?> GetClientAssertionAsync(ClientName? clientName = null,
40+
public async Task<ClientAssertion?> GetClientAssertionAsync(TokenClientName? clientName = null,
4141
TokenRequestParameters? parameters = null,
4242
CancellationToken ct = default)
4343
{

access-token-management/samples/Worker/ClientAssertionService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class ClientAssertionService(IOptionsMonitor<ClientCredentialsClient> opt
3131

3232
private static SigningCredentials Credential = new(new JsonWebKey(RsaKey), "RS256");
3333

34-
public Task<ClientAssertion?> GetClientAssertionAsync(ClientName? clientName, TokenRequestParameters? parameters = null, CancellationToken ct = default)
34+
public Task<ClientAssertion?> GetClientAssertionAsync(TokenClientName? clientName, TokenRequestParameters? parameters = null, CancellationToken ct = default)
3535
{
3636
if (clientName == "demo.jwt")
3737
{

access-token-management/samples/WorkerDI/ClientAssertionService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class ClientAssertionService(IOptionsMonitor<ClientCredentialsClient> opt
3131

3232
private static SigningCredentials Credential = new(new JsonWebKey(RsaKey), "RS256");
3333

34-
public Task<ClientAssertion?> GetClientAssertionAsync(ClientName? clientName = null, TokenRequestParameters? parameters = null, CancellationToken ct = default)
34+
public Task<ClientAssertion?> GetClientAssertionAsync(TokenClientName? clientName = null, TokenRequestParameters? parameters = null, CancellationToken ct = default)
3535
{
3636
if (clientName == "demo.jwt")
3737
{

access-token-management/src/AccessTokenManagement.OpenIdConnect/AccessTokenManagement.OpenIdConnect.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
<ItemGroup>
1919
<ProjectReference Include="..\AccessTokenManagement\AccessTokenManagement.csproj" />
2020
</ItemGroup>
21+
<ItemGroup>
22+
<InternalsVisibleTo Include="AccessTokenManagement.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001002f25809ad9fde9869a3ae4558b897c8a23458393921395b9439e03d6a52afadbf6ff65ef1049cd2ee4ca5501976ad45b453dc3780b8fa7eb39bae755163ef92d53403a0da484b79d24de1bb759eedceb1e13416c734d9c48b226fcd26c18e0a525b68cdba9f2395502d7df5a6d45c2478edd52752511e2924ea209f83aaa23a1" />
23+
</ItemGroup>
24+
2125
</Project>

access-token-management/src/AccessTokenManagement.OpenIdConnect/HttpContextExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ public static async Task<TokenResult<ClientCredentialsToken>> GetClientAccessTok
8585
}
8686

8787
const string AuthenticationPropertiesDPoPKey = ".Token.dpop_proof_key";
88-
internal static void SetProofKey(this AuthenticationProperties properties, ProofKeyString keyString) => properties.Items[AuthenticationPropertiesDPoPKey] = keyString.ToString();
89-
internal static ProofKeyString? GetProofKey(this AuthenticationProperties properties)
88+
internal static void SetProofKey(this AuthenticationProperties properties, DPoPProofKey dpopProofKey) => properties.Items[AuthenticationPropertiesDPoPKey] = dpopProofKey.ToString();
89+
internal static DPoPProofKey? GetProofKey(this AuthenticationProperties properties)
9090
{
9191
if (properties.Items.TryGetValue(AuthenticationPropertiesDPoPKey, out var key))
9292
{
@@ -95,18 +95,18 @@ public static async Task<TokenResult<ClientCredentialsToken>> GetClientAccessTok
9595
return null;
9696
}
9797

98-
return ProofKeyString.Parse(key);
98+
return DPoPProofKey.Parse(key);
9999
}
100100
return null;
101101
}
102102

103103
const string HttpContextDPoPKey = "dpop_proof_key";
104-
internal static void SetCodeExchangeDPoPKey(this HttpContext context, ProofKeyString keyString) => context.Items[HttpContextDPoPKey] = keyString;
105-
internal static ProofKeyString? GetCodeExchangeDPoPKey(this HttpContext context)
104+
internal static void SetCodeExchangeDPoPKey(this HttpContext context, DPoPProofKey dpopProofKey) => context.Items[HttpContextDPoPKey] = dpopProofKey;
105+
internal static DPoPProofKey? GetCodeExchangeDPoPKey(this HttpContext context)
106106
{
107107
if (context.Items.TryGetValue(HttpContextDPoPKey, out var item))
108108
{
109-
return item as ProofKeyString?;
109+
return item as DPoPProofKey?;
110110
}
111111
return null;
112112
}

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/AuthorizationServerDPoPHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ await dPoPNonceStore.StoreNonceAsync(new DPoPNonceContext
111111
/// <summary>
112112
/// Creates a DPoP proof token and attaches it to a request.
113113
/// </summary>
114-
internal async Task SetDPoPProofTokenForCodeExchangeAsync(HttpRequestMessage request, DPoPNonce? dpopNonce = null, ProofKeyString? jwk = null)
114+
internal async Task SetDPoPProofTokenForCodeExchangeAsync(HttpRequestMessage request, DPoPNonce? dpopNonce = null, DPoPProofKey? jwk = null)
115115
{
116116
if (jwk == null)
117117
{
@@ -124,7 +124,7 @@ internal async Task SetDPoPProofTokenForCodeExchangeAsync(HttpRequestMessage req
124124
{
125125
Url = request.GetDPoPUrl(),
126126
Method = request.Method,
127-
ProofKey = jwk.Value,
127+
DPoPProofKey = jwk.Value,
128128
DPoPNonce = dpopNonce,
129129
});
130130

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/OpenIdConnectUserTokenEndpoint.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ public async Task<TokenResult<UserToken>> RefreshAccessTokenAsync(
8484
}
8585
}
8686

87-
var dPoPJsonWebKey = refreshToken.DPoPJsonWebKey;
87+
var dPoPJsonWebKey = refreshToken.DPoPProofKey;
8888
if (dPoPJsonWebKey != null)
8989
{
9090
var proof = await dPoPProofService.CreateProofTokenAsync(new DPoPProof
9191
{
9292
Url = tokenEndpoint,
9393
Method = HttpMethod.Post,
94-
ProofKey = dPoPJsonWebKey.Value,
94+
DPoPProofKey = dPoPJsonWebKey.Value,
9595
}, ct);
9696

9797
request.DPoPProofToken = proof;
@@ -112,7 +112,7 @@ public async Task<TokenResult<UserToken>> RefreshAccessTokenAsync(
112112
{
113113
Url = tokenEndpoint,
114114
Method = HttpMethod.Post,
115-
ProofKey = dPoPJsonWebKey.Value,
115+
DPoPProofKey = dPoPJsonWebKey.Value,
116116
DPoPNonce = DPoPNonce.ParseOrDefault(response.DPoPNonce)
117117
};
118118
var proof = await dPoPProofService.CreateProofTokenAsync(dPoPProofRequest, ct);
@@ -136,8 +136,8 @@ public async Task<TokenResult<UserToken>> RefreshAccessTokenAsync(
136136
metrics.TokenRetrieved(request.ClientId, AccessTokenManagementMetrics.TokenRequestType.User);
137137
var token = new UserToken()
138138
{
139-
IdentityToken = IdentityTokenString.ParseOrDefault(response.IdentityToken),
140-
AccessToken = AccessTokenString.Parse(response.AccessToken ??
139+
IdentityToken = IdentityToken.ParseOrDefault(response.IdentityToken),
140+
AccessToken = AccessToken.Parse(response.AccessToken ??
141141
throw new InvalidOperationException("No access token present")),
142142
AccessTokenType = AccessTokenType.ParseOrDefault(response.TokenType),
143143
DPoPJsonWebKey = dPoPJsonWebKey,
@@ -146,7 +146,7 @@ public async Task<TokenResult<UserToken>> RefreshAccessTokenAsync(
146146
: DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn),
147147
RefreshToken = response.RefreshToken == null
148148
? refreshToken.RefreshToken // use input refresh token if none is returned
149-
: RefreshTokenString.Parse(response.RefreshToken),
149+
: RefreshToken.Parse(response.RefreshToken),
150150
Scope = Scope.ParseOrDefault(response.Scope),
151151
ClientId = oidc.ClientId
152152
};

access-token-management/src/AccessTokenManagement.OpenIdConnect/Internal/StoreTokensInAuthenticationProperties.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public TokenResult<TokenForParameters> GetUserToken(AuthenticationProperties aut
8181
var refreshToken = refreshTokenValue == null
8282
? null
8383
: new UserRefreshToken(
84-
RefreshTokenString.Parse(refreshTokenValue),
85-
ProofKeyString.ParseOrDefault(dpopKey));
84+
RefreshToken.Parse(refreshTokenValue),
85+
DPoPProofKey.ParseOrDefault(dpopKey));
8686

8787
if (accessTokenValue == null && refreshToken != null)
8888
{
@@ -91,13 +91,13 @@ public TokenResult<TokenForParameters> GetUserToken(AuthenticationProperties aut
9191

9292
var userToken = new UserToken
9393
{
94-
AccessToken = AccessTokenString.Parse(accessTokenValue ?? throw new NullReferenceException("access_token should not be null here.")),
94+
AccessToken = AccessToken.Parse(accessTokenValue ?? throw new NullReferenceException("access_token should not be null here.")),
9595
AccessTokenType = AccessTokenType.ParseOrDefault(accessTokenType),
96-
DPoPJsonWebKey = ProofKeyString.ParseOrDefault(dpopKey),
96+
DPoPJsonWebKey = DPoPProofKey.ParseOrDefault(dpopKey),
9797
RefreshToken = refreshToken?.RefreshToken,
9898
Expiration = dtExpires,
9999
ClientId = ClientId.Parse(clientId ?? "unknown"),
100-
IdentityToken = IdentityTokenString.ParseOrDefault(identityTokenValue),
100+
IdentityToken = IdentityToken.ParseOrDefault(identityTokenValue),
101101
};
102102
return new TokenForParameters(userToken, refreshToken);
103103
}

access-token-management/src/AccessTokenManagement.OpenIdConnect/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static IHttpClientBuilder AddUserAccessTokenHttpClient<T>(this IServiceCo
110110
/// Adds a named HTTP client for the factory that automatically sends the current user access token
111111
/// </summary>
112112
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
113-
/// <param name="name">The name of the client.</param>
113+
/// <param name="name">The name of the http client.</param>
114114
/// <param name="parameters"></param>
115115
/// <param name="configureClient">Additional configuration with service provider instance.</param>
116116
/// <returns></returns>

access-token-management/src/AccessTokenManagement.OpenIdConnect/TokenForParameters.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,51 @@
55

66
namespace Duende.AccessTokenManagement.OpenIdConnect;
77

8+
/// <summary>
9+
/// Represents the result when asking for a token for a specific set of parameters, such
10+
/// as scope, resource and possibly others.
11+
///
12+
/// You'll get either an UserToken with an optional UserRefreshToken, or a UserRefreshToken.
13+
///
14+
/// It's not possible to get back an optional user token and no refresh token, because that
15+
/// would be a failure.
16+
///
17+
/// If you get back only a UserRefreshToken, it means that the token for the specified parameters was not found
18+
/// in the cache and you'll need the refresh token to acquire it.
19+
///
20+
/// </summary>
821
public sealed record TokenForParameters
922
{
23+
/// <summary>
24+
/// A token has been found for the specified parameters. If the user has a refresh token,
25+
/// that's also included.
26+
/// </summary>
1027
public TokenForParameters(UserToken tokenForSpecifiedParameters, UserRefreshToken? refreshToken)
1128
{
1229
TokenForSpecifiedParameters = tokenForSpecifiedParameters;
1330
RefreshToken = refreshToken;
1431
NoRefreshToken = refreshToken == null;
1532
}
1633

34+
/// <summary>
35+
/// No token has been found for the specified parameters. The refresh token can be used
36+
/// to get one.
37+
/// </summary>
38+
/// <param name="refreshToken"></param>
1739
public TokenForParameters(UserRefreshToken refreshToken)
1840
{
1941
RefreshToken = refreshToken;
2042
NoRefreshToken = false;
2143
}
2244

45+
/// <summary>
46+
/// The token for the specified parameters. This is the token that was found in the cache.
47+
/// </summary>
2348
public UserToken? TokenForSpecifiedParameters { get; }
49+
50+
/// <summary>
51+
/// The user's refresh token.
52+
/// </summary>
2453
public UserRefreshToken? RefreshToken { get; }
2554

2655
[MemberNotNullWhen(true, nameof(TokenForSpecifiedParameters))]

access-token-management/src/AccessTokenManagement.OpenIdConnect/UserRefreshToken.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@
66

77
namespace Duende.AccessTokenManagement.OpenIdConnect;
88

9-
public sealed record UserRefreshToken(RefreshTokenString RefreshToken, ProofKeyString? DPoPJsonWebKey);
9+
/// <summary>
10+
/// A record that captures the information to refresh an access token for a user.
11+
///
12+
/// Minimally, you need a refresh token. If you use dpop, you'll also need the dpop proof key
13+
/// </summary>
14+
public sealed record UserRefreshToken(RefreshToken RefreshToken, DPoPProofKey? DPoPProofKey);

access-token-management/src/AccessTokenManagement.OpenIdConnect/UserToken.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,49 @@
66

77
namespace Duende.AccessTokenManagement.OpenIdConnect;
88

9+
/// <summary>
10+
/// A token that's tied to a speficic user. It likely contains claims like
11+
/// sub.
12+
/// </summary>
913
public sealed record UserToken : AccessTokenRequestHandler.IToken
1014
{
11-
public required AccessTokenString AccessToken { get; init; }
15+
/// <summary>
16+
/// The access token that was requested.
17+
/// </summary>
18+
public required AccessToken AccessToken { get; init; }
1219

13-
public ProofKeyString? DPoPJsonWebKey { get; init; }
20+
public DPoPProofKey? DPoPJsonWebKey { get; init; }
1421

22+
/// <summary>
23+
/// Indicates when the token expires. If no expiration is used,
24+
/// this value is set to DateTimeOffset.MaxValue.
25+
/// </summary>
1526
public required DateTimeOffset Expiration { get; init; }
1627

28+
/// <summary>
29+
/// The scope that was assigned to the token when requesting it.
30+
/// </summary>
1731
public Scope? Scope { get; init; }
1832

33+
/// <summary>
34+
/// The OIDC Client that was used to acquire the token. This is not the same as the client that was used to acquire the
35+
/// </summary>
1936
public required ClientId ClientId { get; init; }
2037

38+
/// <summary>
39+
/// The type of access token. Typically maps to Bearer or DPoP.
40+
/// </summary>
2141
public required AccessTokenType? AccessTokenType { get; init; }
2242

23-
public required RefreshTokenString? RefreshToken { get; init; }
43+
/// <summary>
44+
/// The refresh token that the user may have if offline access was requested.
45+
/// </summary>
46+
public required RefreshToken? RefreshToken { get; init; }
2447

2548
/// <summary>
2649
/// The identity token that may be populated by the OP when refreshing the access token. This
2750
/// value is not stored, but available should some OP's require to send this value, for example
2851
/// during logout.
2952
/// </summary>
30-
public required IdentityTokenString? IdentityToken { get; init; }
53+
public required IdentityToken? IdentityToken { get; init; }
3154
}

access-token-management/src/AccessTokenManagement.OpenIdConnect/UserTokenManagementOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ public sealed class UserTokenManagementOptions
5050
/// <summary>
5151
/// The string representation of the JSON web key to use for DPoP.
5252
/// </summary>
53-
public ProofKeyString? DPoPJsonWebKey { get; set; }
53+
public DPoPProofKey? DPoPJsonWebKey { get; set; }
5454
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Text.Json.Serialization;
6+
using Duende.AccessTokenManagement.Internal;
7+
8+
namespace Duende.AccessTokenManagement;
9+
10+
/// <summary>
11+
/// Strongly typed representation of an access token. This can be a Client Credentials or User token.
12+
/// </summary>
13+
[JsonConverter(typeof(StringValueJsonConverter<AccessToken>))]
14+
public readonly record struct AccessToken : IStronglyTypedValue<AccessToken>
15+
{
16+
public override string ToString() => Value;
17+
18+
// Officially, there's no max length for JWTs, but 32k is a good limit
19+
public const int MaxLength = 32 * 1024; // 32k
20+
21+
private static readonly ValidationRule<string>[] Validators = [
22+
ValidationRules.MaxLength(MaxLength)
23+
];
24+
25+
public static implicit operator AccessToken(string value) => Parse(value);
26+
public static implicit operator string (AccessToken value) => value.ToString();
27+
28+
public AccessToken() => throw new InvalidOperationException("Can't create null value");
29+
private AccessToken(string value) => Value = value;
30+
31+
private string Value { get; }
32+
33+
public static bool TryParse(string value, [NotNullWhen(true)] out AccessToken? parsed, out string[] errors) =>
34+
IStronglyTypedValue<AccessToken>.TryBuildValidatedObject(value, Validators, out parsed, out errors);
35+
36+
37+
static AccessToken IStronglyTypedValue<AccessToken>.Create(string result) => new(result);
38+
39+
public static AccessToken Parse(string value) => StringParsers<AccessToken>.Parse(value);
40+
}

access-token-management/src/AccessTokenManagement/AccessTokenRequestHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ Task<TokenResult<IToken>> GetTokenAsync(
9494
/// </summary>
9595
public interface IToken
9696
{
97-
AccessTokenString AccessToken { get; }
97+
AccessToken AccessToken { get; }
9898

9999
/// <summary>
100100
/// The string representation of the JSON web key to use for DPoP.
101101
/// </summary>
102-
ProofKeyString? DPoPJsonWebKey { get; }
102+
DPoPProofKey? DPoPJsonWebKey { get; }
103103

104104
/// <summary>
105105
/// The Client id that this token was originally requested for.

access-token-management/src/AccessTokenManagement/AccessTokenString.cs

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)