Skip to content

Commit b35342d

Browse files
Gjermund StensrudErwinvandervalk
Gjermund Stensrud
authored andcommitted
Add support for additional claims in DPoP proof payload
1 parent 0e73586 commit b35342d

File tree

6 files changed

+54
-0
lines changed

6 files changed

+54
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ protected virtual async Task<bool> SetDPoPProofTokenAsync(HttpRequestMessage req
127127

128128
if (!string.IsNullOrEmpty(token.DPoPJsonWebKey))
129129
{
130+
request.Options.TryGetValue(new HttpRequestOptionsKey<IReadOnlyDictionary<string, string>>("Duende.AccessTokenManagement.DPoPProofAdditionalPayloadClaims"), out var additionalClaims);
131+
130132
// create proof
131133
var proofToken = await _dPoPProofService.CreateProofTokenAsync(new DPoPProofRequest
132134
{
@@ -135,6 +137,7 @@ protected virtual async Task<bool> SetDPoPProofTokenAsync(HttpRequestMessage req
135137
Method = request.Method.ToString(),
136138
DPoPJsonWebKey = token.DPoPJsonWebKey,
137139
DPoPNonce = dpopNonce,
140+
AdditionalPayloadClaims = additionalClaims,
138141
});
139142

140143
if (proofToken != null)

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,14 @@ public static string GetDPoPUrl(this HttpRequestMessage request)
7777
{
7878
return request.RequestUri!.Scheme + "://" + request.RequestUri!.Authority + request.RequestUri!.LocalPath;
7979
}
80+
81+
/// <summary>
82+
/// Additional claims that will be added to the DPoP proof payload on generation
83+
/// </summary>
84+
/// <param name="request"></param>
85+
/// <param name="customClaims"></param>
86+
public static void AddDPoPProofAdditionalPayloadClaims(this HttpRequestMessage request, IDictionary<string, string> customClaims)
87+
{
88+
request.Options.TryAdd("Duende.AccessTokenManagement.DPoPProofAdditionalPayloadClaims", customClaims.AsReadOnly());
89+
}
8090
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ await _dPoPNonceStore.StoreNonceAsync(new DPoPNonceContext
119119
payload.Add(JwtClaimTypes.Nonce, nonce);
120120
}
121121

122+
if (request.AdditionalPayloadClaims?.Count > 0)
123+
{
124+
foreach (var claim in request.AdditionalPayloadClaims)
125+
{
126+
payload.Add(claim.Key, claim.Value);
127+
}
128+
}
129+
122130
var handler = new JsonWebTokenHandler() { SetDefaultTimesOnTokenCreation = false };
123131
var key = new SigningCredentials(jsonWebKey, jsonWebKey.Alg);
124132
var proofToken = handler.CreateToken(JsonSerializer.Serialize(payload), key, header);

access-token-management/src/AccessTokenManagement/Interfaces/IDPoPProofService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public class DPoPProofRequest
4848
/// The access token
4949
/// </summary>
5050
public string? AccessToken { get; set; }
51+
52+
/// <summary>
53+
/// Additional claims to add to the DPoP proof payload
54+
/// </summary>
55+
public IReadOnlyDictionary<string, string>? AdditionalPayloadClaims { get; set; }
5156
}
5257

5358
/// <summary>

access-token-management/test/AccessTokenManagement.Tests/ClientTokenManagementApiTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,32 @@ public async Task dpop_tokens_should_be_passed_to_api()
189189
proofToken.ShouldNotBeNull();
190190
}
191191

192+
[Fact]
193+
public async Task when_additional_proof_payload_claims_are_defined_they_should_be_included_in_dpop_proof()
194+
{
195+
string? proofToken = null;
196+
197+
ApiHost.ApiInvoked += ctx =>
198+
{
199+
proofToken = ctx.Request.Headers["DPoP"].FirstOrDefault()?.ToString();
200+
};
201+
var client = _clientFactory.CreateClient("test");
202+
203+
var requestMessage = new HttpRequestMessage(HttpMethod.Get, ApiHost.Url("/test"));
204+
requestMessage.AddDPoPProofAdditionalPayloadClaims(new Dictionary<string, string>() {
205+
{ "claim_one", "one" },
206+
{ "claim_two", "two" },
207+
});
208+
209+
var apiResult = await client.SendAsync(requestMessage);
210+
211+
proofToken.ShouldNotBeNull();
212+
var payload = Base64UrlEncoder.Decode(proofToken!.Split('.')[1]);
213+
var values = JsonSerializer.Deserialize<Dictionary<string, object>>(payload);
214+
values!["claim_one"].ToString().ShouldBe("one");
215+
values!["claim_two"].ToString().ShouldBe("two");
216+
}
217+
192218
[Fact]
193219
public async Task when_api_issues_nonce_api_request_should_be_retried_with_new_nonce()
194220
{

access-token-management/test/AccessTokenManagement.Tests/PublicApiVerificationTests.VerifyPublicApi.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
}
6363
public static class DPoPExtensions
6464
{
65+
public static void AddDPoPProofAdditionalPayloadClaims(this System.Net.Http.HttpRequestMessage request, System.Collections.Generic.IDictionary<string, string> customClaims) { }
6566
public static void ClearDPoPProofToken(this System.Net.Http.HttpRequestMessage request) { }
6667
public static string? GetDPoPNonce(this System.Net.Http.HttpResponseMessage response) { }
6768
public static string GetDPoPUrl(this System.Net.Http.HttpRequestMessage request) { }
@@ -88,6 +89,7 @@
8889
{
8990
public DPoPProofRequest() { }
9091
public string? AccessToken { get; set; }
92+
public System.Collections.Generic.IReadOnlyDictionary<string, string>? AdditionalPayloadClaims { get; set; }
9193
public string DPoPJsonWebKey { get; set; }
9294
public string? DPoPNonce { get; set; }
9395
public string Method { get; set; }

0 commit comments

Comments
 (0)