Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit a92cadd

Browse files
authored
Merge pull request #90 from DuendeSoftware/joe/dpop-resources-2.1
Fix DPoP proof token creation when resources are used
2 parents db3ac68 + 9b7eaf2 commit a92cadd

File tree

7 files changed

+55
-51
lines changed

7 files changed

+55
-51
lines changed

samples/Web/Controllers/HomeController.cs

+11-17
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using Microsoft.AspNetCore.Authorization;
1+
using Microsoft.AspNetCore.Authentication;
2+
using Microsoft.AspNetCore.Authorization;
23
using Microsoft.AspNetCore.Mvc;
34
using System.Net.Http;
45
using System.Text.Json;
56
using System.Threading.Tasks;
6-
using Duende.AccessTokenManagement.OpenIdConnect;
77
using IdentityModel.Client;
8-
using Microsoft.AspNetCore.Authentication;
8+
using Duende.AccessTokenManagement.OpenIdConnect;
99

1010
namespace Web.Controllers;
1111

@@ -33,7 +33,7 @@ public async Task<IActionResult> CallApiAsUserManual()
3333
var client = _httpClientFactory.CreateClient();
3434
client.SetToken(token.AccessTokenType!, token.AccessToken!);
3535

36-
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
36+
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}test");
3737
ViewBag.Json = PrettyPrint(response);
3838

3939
return View("CallApi");
@@ -45,15 +45,15 @@ public async Task<IActionResult> CallApiAsUserExtensionMethod()
4545
var client = _httpClientFactory.CreateClient();
4646
client.SetToken(token.AccessTokenType!, token.AccessToken!);
4747

48-
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
48+
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}test");
4949
ViewBag.Json = PrettyPrint(response);
5050

5151
return View("CallApi");
5252
}
5353

5454
public async Task<IActionResult> CallApiAsUserFactory()
5555
{
56-
var client = _httpClientFactory.CreateClient("user_client");
56+
var client = _httpClientFactory.CreateClient("user");
5757

5858
var response = await client.GetStringAsync("test");
5959
ViewBag.Json = PrettyPrint(response);
@@ -72,11 +72,8 @@ public async Task<IActionResult> CallApiAsUserFactoryTyped([FromServices] TypedU
7272
[AllowAnonymous]
7373
public async Task<IActionResult> CallApiAsUserResourceIndicator()
7474
{
75-
var token = await HttpContext.GetUserAccessTokenAsync(new UserTokenRequestParameters { Resource = "urn:resource1" });
76-
var client = _httpClientFactory.CreateClient();
77-
client.SetToken(token.AccessTokenType!, token.AccessToken!);
78-
79-
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
75+
var client = _httpClientFactory.CreateClient("user-resource");
76+
var response = await client.GetStringAsync("test");
8077

8178
ViewBag.Json = PrettyPrint(response);
8279
return View("CallApi");
@@ -90,7 +87,7 @@ public async Task<IActionResult> CallApiAsClientExtensionMethod()
9087
var client = _httpClientFactory.CreateClient();
9188
client.SetToken(token.AccessTokenType!, token.AccessToken!);
9289

93-
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
90+
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}test");
9491

9592
ViewBag.Json = PrettyPrint(response);
9693
return View("CallApi");
@@ -99,11 +96,8 @@ public async Task<IActionResult> CallApiAsClientExtensionMethod()
9996
[AllowAnonymous]
10097
public async Task<IActionResult> CallApiAsClientResourceIndicator()
10198
{
102-
var token = await HttpContext.GetClientAccessTokenAsync(new UserTokenRequestParameters { Resource = "urn:resource1" });
103-
var client = _httpClientFactory.CreateClient();
104-
client.SetToken(token.AccessTokenType!, token.AccessToken!);
105-
106-
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
99+
var client = _httpClientFactory.CreateClient("client-resource");
100+
var response = await client.GetStringAsync("test");
107101

108102
ViewBag.Json = PrettyPrint(response);
109103
return View("CallApi");

samples/Web/Startup.cs

+32-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Security.Cryptography;
66
using System.Text.Json;
77
using System.Threading.Tasks;
8+
using Duende.AccessTokenManagement.OpenIdConnect;
89
using Microsoft.AspNetCore.Authentication;
910
using Microsoft.AspNetCore.Builder;
1011
using Microsoft.Extensions.DependencyInjection;
@@ -16,11 +17,14 @@ namespace Web;
1617

1718
public static class Startup
1819
{
19-
public const bool UseDPoP = false;
20+
public const bool UseDPoP = true;
21+
22+
public const string BaseUrl = "https://localhost:5001";
23+
//public const string BaseUrl = "https://demo.duendesoftware.com";
2024

2125
public const string ApiBaseUrl = UseDPoP ?
22-
"https://demo.duendesoftware.com/api/dpop/" :
23-
"https://demo.duendesoftware.com/api/";
26+
$"{BaseUrl}/api/dpop/" :
27+
$"{BaseUrl}/api/";
2428

2529
internal static WebApplication ConfigureServices(this WebApplicationBuilder builder)
2630
{
@@ -39,8 +43,7 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
3943
})
4044
.AddOpenIdConnect("oidc", options =>
4145
{
42-
options.Authority = "https://demo.duendesoftware.com";
43-
//options.Authority = "https://localhost:5001";
46+
options.Authority = BaseUrl;
4447

4548
options.ClientId = "interactive.confidential.short";
4649
options.ClientSecret = "secret";
@@ -56,6 +59,8 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
5659
options.Scope.Add("api");
5760
options.Scope.Add("resource1.scope1");
5861

62+
options.Resource = "urn:resource1";
63+
5964
options.GetClaimsFromUserInfoEndpoint = true;
6065
options.SaveTokens = true;
6166
options.MapInboundClaims = false;
@@ -65,12 +70,6 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
6570
NameClaimType = "name",
6671
RoleClaimType = "role"
6772
};
68-
69-
options.Events.OnRedirectToIdentityProvider = ctx =>
70-
{
71-
ctx.ProtocolMessage.Resource = "urn:resource1";
72-
return Task.CompletedTask;
73-
};
7473
});
7574

7675
var rsaKey = new RsaSecurityKey(RSA.Create(2048));
@@ -80,11 +79,22 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
8079

8180
builder.Services.AddOpenIdConnectAccessTokenManagement(options =>
8281
{
83-
options.DPoPJsonWebKey = UseDPoP ? jwk : null; ;
82+
options.DPoPJsonWebKey = UseDPoP ? jwk : null;
8483
});
8584

8685
// registers HTTP client that uses the managed user access token
87-
builder.Services.AddUserAccessTokenHttpClient("user_client",
86+
builder.Services.AddUserAccessTokenHttpClient("user",
87+
configureClient: client => {
88+
client.BaseAddress = new Uri(ApiBaseUrl);
89+
});
90+
91+
// registers HTTP client that uses the managed user access token and
92+
// includes a resource indicator
93+
builder.Services.AddUserAccessTokenHttpClient("user-resource",
94+
new UserTokenRequestParameters
95+
{
96+
Resource = "urn:resource1"
97+
},
8898
configureClient: client => {
8999
client.BaseAddress = new Uri(ApiBaseUrl);
90100
});
@@ -93,6 +103,15 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
93103
builder.Services.AddClientAccessTokenHttpClient("client",
94104
configureClient: client => { client.BaseAddress = new Uri(ApiBaseUrl); });
95105

106+
// registers HTTP client that uses the managed client access token and
107+
// includes a resource indicator
108+
builder.Services.AddClientAccessTokenHttpClient("client-resource",
109+
new UserTokenRequestParameters
110+
{
111+
Resource = "urn:resource1"
112+
},
113+
configureClient: client => { client.BaseAddress = new Uri(ApiBaseUrl); });
114+
96115
// registers a typed HTTP client with token management support
97116
builder.Services.AddHttpClient<TypedUserClient>(client =>
98117
{

samples/Web/Views/Home/Index.cshtml

+2-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,5 @@
1212
<a asp-controller="Home" asp-action="CallApiAsClientFactory">HTTP client factory</a>
1313
@("|")
1414
<a asp-controller="Home" asp-action="CallApiAsClientFactoryTyped">HTTP client factory (typed)</a>
15-
@if (!Startup.UseDPoP)
16-
{
17-
@("|")
18-
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
19-
}
15+
@("|")
16+
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>

samples/Web/Views/Home/Secure.cshtml

+4-10
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212
<a asp-controller="Home" asp-action="CallApiAsUserFactory">HTTP client factory</a>
1313
@("|")
1414
<a asp-controller="Home" asp-action="CallApiAsUserFactoryTyped">HTTP client factory (typed)</a>
15-
@if (!Startup.UseDPoP)
16-
{
17-
@("|")
18-
<a asp-controller="Home" asp-action="CallApiAsUserResourceIndicator">Use resource indicator</a>
19-
}
15+
@("|")
16+
<a asp-controller="Home" asp-action="CallApiAsUserResourceIndicator">Use resource indicator</a>
2017

2118
<h3>Call API as Client</h3>
2219

@@ -28,11 +25,8 @@
2825
<a asp-controller="Home" asp-action="CallApiAsClientFactory">HTTP client factory</a>
2926
@("|")
3027
<a asp-controller="Home" asp-action="CallApiAsClientFactoryTyped">HTTP client factory (typed)</a>
31-
@if (!Startup.UseDPoP)
32-
{
33-
@("|")
34-
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
35-
}
28+
@("|")
29+
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
3630

3731
<h2>Claims</h2>
3832

src/Duende.AccessTokenManagement.OpenIdConnect/AuthenticationSessionUserTokenStore.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,12 @@ public async Task<UserToken> GetTokenAsync(
8989

9090
var tokenName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.AccessToken, parameters);
9191
var tokenTypeName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.TokenType, parameters);
92-
var dpopKeyName = NamePrefixAndResourceSuffix(DPoPKeyName, parameters);
9392
var expiresName = NamePrefixAndResourceSuffix("expires_at", parameters);
9493

95-
// Note that we are not including the the resource suffix because there is no per-resource refresh token
94+
// Note that we are not including the the resource suffix because
95+
// there is no per-resource refresh token or dpop key
9696
var refreshTokenName = NamePrefix(OpenIdConnectParameterNames.RefreshToken);
97+
var dpopKeyName = NamePrefix(DPoPKeyName);
9798

9899
var appendChallengeScheme = AppendChallengeSchemeToTokenNames(parameters);
99100

@@ -189,12 +190,12 @@ public async Task StoreTokenAsync(
189190

190191
var tokenName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.AccessToken, parameters);
191192
var tokenTypeName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.TokenType, parameters);
192-
var dpopKeyName = NamePrefixAndResourceSuffix(DPoPKeyName, parameters);
193193
var expiresName = NamePrefixAndResourceSuffix("expires_at", parameters);
194194

195195
// Note that we are not including the resource suffix because there
196-
// is no per-resource refresh token
196+
// is no per-resource refresh token or dpop key
197197
var refreshTokenName = NamePrefix(OpenIdConnectParameterNames.RefreshToken);
198+
var dpopKeyName = NamePrefix(DPoPKeyName);
198199

199200
if (AppendChallengeSchemeToTokenNames(parameters))
200201
{

src/Duende.AccessTokenManagement.OpenIdConnect/TokenManagementHttpContextExtensions.cs

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Duende.AccessTokenManagement;
1010
using Duende.AccessTokenManagement.OpenIdConnect;
1111
using Microsoft.Extensions.Options;
12-
using Microsoft.Extensions.Logging;
1312

1413
namespace Microsoft.AspNetCore.Authentication;
1514

src/Duende.AccessTokenManagement/AccessTokenHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ protected virtual async Task SetTokenAsync(HttpRequestMessage request, bool forc
103103
}
104104

105105
// since AccessTokenType above in the token endpoint response (the token_type value) could be case insensitive, but
106-
// when we send it as an Authoriization header in the API request it must be case sensitive, we
106+
// when we send it as an Authorization header in the API request it must be case sensitive, we
107107
// are checking for that here and forcing it to the exact casing required.
108108
if (scheme.Equals(AuthenticationSchemes.AuthorizationHeaderBearer, System.StringComparison.OrdinalIgnoreCase))
109109
{

0 commit comments

Comments
 (0)