Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ namespace Duende.IdentityModel.OidcClient.DPoP;
/// <summary>
/// Used to create DPoP proof tokens.
/// </summary>
public class DPoPProofTokenFactory
public class DefaultDPoPProofTokenFactory : IDPoPProofTokenFactory
{
private readonly JsonWebKey _jwk;

/// <summary>
/// Constructor
/// </summary>
public DPoPProofTokenFactory(string proofKey)
public DefaultDPoPProofTokenFactory(string proofKey)
{
_jwk = new JsonWebKey(proofKey);

Expand Down Expand Up @@ -79,7 +79,7 @@ public DPoPProof CreateProofToken(DPoPProofRequest request)

if (!string.IsNullOrWhiteSpace(request.AccessToken))
{
// ath: hash of the access token. The value MUST be the result of a base64url encoding
// ath: hash of the access token. The value MUST be the result of a base64url encoding
// the SHA-256 hash of the ASCII encoding of the associated access token's value.
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.ASCII.GetBytes(request.AccessToken));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Duende Software. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

namespace Duende.IdentityModel.OidcClient.DPoP;

public interface IDPoPProofTokenFactory
{
DPoPProof CreateProofToken(DPoPProofRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,27 @@ public static void ConfigureDPoP(this OidcClientOptions options,
HttpMessageHandler? tokenEndpointInnerHandler = null,
HttpMessageHandler? apiInnerHandler = null)
{
var tokenDpopHandler = new ProofTokenMessageHandler(proofKey, tokenEndpointInnerHandler ?? new HttpClientHandler());
var apiDpopHandler = new ProofTokenMessageHandler(proofKey, apiInnerHandler ?? new HttpClientHandler());
var tokenDpopHandler = new ProofTokenMessageHandler(new DefaultDPoPProofTokenFactory(proofKey), tokenEndpointInnerHandler ?? new HttpClientHandler());
var apiDpopHandler = new ProofTokenMessageHandler(new DefaultDPoPProofTokenFactory(proofKey), apiInnerHandler ?? new HttpClientHandler());

options.BackchannelHandler = tokenDpopHandler;
options.RefreshTokenInnerHttpHandler = apiDpopHandler;
}

/// <summary>
/// Configure back-channel handlers for DPoP
/// </summary>
/// <param name="options">The OidcClient options</param>
/// <param name="proofTokenFactory">An instance of an implementation of <see cref="IDPoPProofTokenFactory"/></param>
/// <param name="tokenEndpointInnerHandler">The inner handler for the token endpoint (optional)</param>
/// <param name="apiInnerHandler">The inner handler for API calls (optional)</param>
public static void ConfigureDPoP(this OidcClientOptions options,
IDPoPProofTokenFactory proofTokenFactory,
HttpMessageHandler? tokenEndpointInnerHandler = null,
HttpMessageHandler? apiInnerHandler = null)
{
var tokenDpopHandler = new ProofTokenMessageHandler(proofTokenFactory, tokenEndpointInnerHandler ?? new HttpClientHandler());
var apiDpopHandler = new ProofTokenMessageHandler(proofTokenFactory, apiInnerHandler ?? new HttpClientHandler());

options.BackchannelHandler = tokenDpopHandler;
options.RefreshTokenInnerHttpHandler = apiDpopHandler;
Expand All @@ -35,12 +54,37 @@ public static void ConfigureDPoP(this OidcClientOptions options,
/// <param name="refreshToken">The refresh token</param>
/// <param name="apiInnerHandler">The inner handler (optional)</param>
/// <returns></returns>
public static HttpMessageHandler CreateDPoPHandler(this Duende.IdentityModel.OidcClient.OidcClient client,
public static HttpMessageHandler CreateDPoPHandler(this OidcClient client,
string proofKey,
string refreshToken,
HttpMessageHandler? apiInnerHandler = null)
{
var apiDpopHandler = new ProofTokenMessageHandler(proofKey, apiInnerHandler ?? new HttpClientHandler());
var apiDpopHandler = new ProofTokenMessageHandler(new DefaultDPoPProofTokenFactory(proofKey), apiInnerHandler ?? new HttpClientHandler());

var handler = new RefreshTokenDelegatingHandler(
client,
null,
refreshToken,
"DPoP",
apiDpopHandler);

return handler;
}

/// <summary>
/// Creates a handler for API calls using DPoP and automatic refresh token management
/// </summary>
/// <param name="client">The OidcClient instance</param>
/// <param name="proofTokenFactory">An instance of an implementation of <see cref="IDPoPProofTokenFactory"/></param>
/// <param name="refreshToken">The refresh token</param>
/// <param name="apiInnerHandler">The inner handler (optional)</param>
/// <returns></returns>
public static HttpMessageHandler CreateDPoPHandler(this OidcClient client,
IDPoPProofTokenFactory proofTokenFactory,
string refreshToken,
HttpMessageHandler? apiInnerHandler = null)
{
var apiDpopHandler = new ProofTokenMessageHandler(proofTokenFactory, apiInnerHandler ?? new HttpClientHandler());

var handler = new RefreshTokenDelegatingHandler(
client,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ namespace Duende.IdentityModel.OidcClient.DPoP;
/// </summary>
public class ProofTokenMessageHandler : DelegatingHandler
{
private readonly DPoPProofTokenFactory _proofTokenFactory;
private readonly IDPoPProofTokenFactory _proofTokenFactory;
private string? _nonce;

/// <summary>
/// Constructor
/// </summary>
public ProofTokenMessageHandler(string proofKey, HttpMessageHandler innerHandler)
public ProofTokenMessageHandler(IDPoPProofTokenFactory dPoPProofTokenFactory, HttpMessageHandler innerHandler)
{
_proofTokenFactory = new DPoPProofTokenFactory(proofKey);
_proofTokenFactory = dPoPProofTokenFactory ?? throw new ArgumentNullException(nameof(dPoPProofTokenFactory));
InnerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class DPoPTest : IntegrationTestBase
{

private static readonly string _jwkJson;
private readonly IDPoPProofTokenFactory _proofTokenFactory;
private readonly IdentityServer.Models.Client _client;

static DPoPTest()
Expand All @@ -39,12 +40,14 @@ public DPoPTest()
AllowedScopes = { "scope1" },
RequireDPoP = true,
});

_proofTokenFactory = new DefaultDPoPProofTokenFactory(_jwkJson);
}

[Fact]
public async Task dpop_tokens_should_be_passed_to_token_endpoint()
{
var handler = new ProofTokenMessageHandler(_jwkJson, IdentityServerHost.Server.CreateHandler());
var handler = new ProofTokenMessageHandler(_proofTokenFactory, IdentityServerHost.Server.CreateHandler());
var client = new HttpClient(handler);

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
Expand All @@ -63,7 +66,7 @@ public async Task when_nonce_required_nonce_should_be_used_for_token_endpoint()
{
_client.DPoPValidationMode = DPoPTokenExpirationValidationMode.Nonce;

var handler = new ProofTokenMessageHandler(_jwkJson, IdentityServerHost.Server.CreateHandler());
var handler = new ProofTokenMessageHandler(_proofTokenFactory, IdentityServerHost.Server.CreateHandler());
var client = new HttpClient(handler);

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
Expand All @@ -80,7 +83,7 @@ public async Task when_nonce_required_nonce_should_be_used_for_token_endpoint()
[Fact]
public async Task dpop_tokens_should_be_passed_to_api()
{
var tokenHandler = new ProofTokenMessageHandler(_jwkJson, IdentityServerHost.Server.CreateHandler());
var tokenHandler = new ProofTokenMessageHandler(_proofTokenFactory, IdentityServerHost.Server.CreateHandler());
var tokenClient = new HttpClient(tokenHandler);

var tokenResponse = await tokenClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
Expand All @@ -90,7 +93,7 @@ public async Task dpop_tokens_should_be_passed_to_api()
ClientSecret = "secret",
});

var apiHandler = new ProofTokenMessageHandler(_jwkJson, ApiHost.Server.CreateHandler());
var apiHandler = new ProofTokenMessageHandler(_proofTokenFactory, ApiHost.Server.CreateHandler());
var apiClient = new HttpClient(apiHandler);
apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("DPoP", tokenResponse.AccessToken);

Expand All @@ -110,7 +113,7 @@ public async Task when_nonce_required_nonce_should_be_used_for_api_endpoint()
ApiHost.ValidateNonce = true;
await ApiHost.InitializeAsync();

var tokenHandler = new ProofTokenMessageHandler(_jwkJson, IdentityServerHost.Server.CreateHandler());
var tokenHandler = new ProofTokenMessageHandler(_proofTokenFactory, IdentityServerHost.Server.CreateHandler());
var tokenClient = new HttpClient(tokenHandler);

var tokenResponse = await tokenClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
Expand All @@ -120,7 +123,7 @@ public async Task when_nonce_required_nonce_should_be_used_for_api_endpoint()
ClientSecret = "secret",
});

var apiHandler = new ProofTokenMessageHandler(_jwkJson, ApiHost.Server.CreateHandler());
var apiHandler = new ProofTokenMessageHandler(_proofTokenFactory, ApiHost.Server.CreateHandler());
var apiClient = new HttpClient(apiHandler);
apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("DPoP", tokenResponse.AccessToken);

Expand Down