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

Commit 951f552

Browse files
authored
Merge pull request #35 from DuendeSoftware/brock/resource-param
Fix bug where use of resource param is not triggering renewal
2 parents 99b8520 + 93ada28 commit 951f552

File tree

8 files changed

+61
-19
lines changed

8 files changed

+61
-19
lines changed

samples/Web/Controllers/HomeController.cs

+28
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ public async Task<IActionResult> CallApiAsUserFactoryTyped([FromServices] TypedU
6969
return View("CallApi");
7070
}
7171

72+
[AllowAnonymous]
73+
public async Task<IActionResult> CallApiAsUserResourceIndicator()
74+
{
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("https://demo.duendesoftware.com/api/test");
80+
81+
ViewBag.Json = PrettyPrint(response);
82+
return View("CallApi");
83+
}
84+
85+
7286
[AllowAnonymous]
7387
public async Task<IActionResult> CallApiAsClientExtensionMethod()
7488
{
@@ -81,7 +95,21 @@ public async Task<IActionResult> CallApiAsClientExtensionMethod()
8195
ViewBag.Json = PrettyPrint(response);
8296
return View("CallApi");
8397
}
98+
99+
[AllowAnonymous]
100+
public async Task<IActionResult> CallApiAsClientResourceIndicator()
101+
{
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("https://demo.duendesoftware.com/api/test");
107+
108+
ViewBag.Json = PrettyPrint(response);
109+
return View("CallApi");
110+
}
84111

112+
85113
[AllowAnonymous]
86114
public async Task<IActionResult> CallApiAsClientFactory()
87115
{

samples/Web/Properties/launchSettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"Web": {
44
"commandName": "Project",
55
"launchBrowser": true,
6-
"applicationUrl": "https://localhost:5001;http://localhost:5000",
6+
"applicationUrl": "https://localhost:5002",
77
"environmentVariables": {
88
"ASPNETCORE_ENVIRONMENT": "Development"
99
}

samples/Web/Startup.cs

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Security.Cryptography;
66
using System.Text.Json;
7+
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Authentication;
89
using Microsoft.AspNetCore.Builder;
910
using Microsoft.Extensions.DependencyInjection;
@@ -33,6 +34,7 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
3334
.AddOpenIdConnect("oidc", options =>
3435
{
3536
options.Authority = "https://demo.duendesoftware.com";
37+
//options.Authority = "https://localhost:5001";
3638

3739
options.ClientId = "interactive.confidential.short";
3840
options.ClientSecret = "secret";
@@ -46,6 +48,7 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
4648
options.Scope.Add("email");
4749
options.Scope.Add("offline_access");
4850
options.Scope.Add("api");
51+
options.Scope.Add("resource1.scope1");
4952

5053
options.GetClaimsFromUserInfoEndpoint = true;
5154
options.SaveTokens = true;
@@ -56,6 +59,12 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
5659
NameClaimType = "name",
5760
RoleClaimType = "role"
5861
};
62+
63+
options.Events.OnRedirectToIdentityProvider = ctx =>
64+
{
65+
ctx.ProtocolMessage.Resource = "urn:resource1";
66+
return Task.CompletedTask;
67+
};
5968
});
6069

6170
var rsaKey = new RsaSecurityKey(RSA.Create(2048));

samples/Web/Views/Home/Index.cshtml

+2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@
99
<a href="./home/CallApiAsClientFactory">HTTP client factory</a>
1010
|
1111
<a href="./home/CallApiAsClientFactoryTyped">HTTP client factory (typed)</a>
12+
|
13+
<a href="./home/CallApiAsClientResourceIndicator">Use resource indicator</a>
1214

1315

samples/Web/Views/Home/Secure.cshtml

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<a href="./CallApiAsUserFactory">HTTP client factory</a>
1010
|
1111
<a href="./CallApiAsUserFactoryTyped">HTTP client factory (typed)</a>
12+
|
13+
<a href="./CallApiAsUserResourceIndicator">Use resource indicator</a>
1214

1315
<h3>Call API as Client</h3>
1416

@@ -17,7 +19,8 @@
1719
<a href="./CallApiAsClientFactory">HTTP client factory</a>
1820
|
1921
<a href="./CallApiAsClientFactoryTypes">HTTP client factory (typed)</a>
20-
22+
|
23+
<a href="./CallApiAsClientResourceIndicator">Use resource indicator</a>
2124

2225
<h2>Claims</h2>
2326

src/Duende.AccessTokenManagement.OpenIdConnect/AuthenticationSessionUserTokenStore.cs

+13-14
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,14 @@ public async Task<UserToken> GetTokenAsync(
102102
{
103103
dpopKeyName += $"::{parameters.Resource}";
104104
}
105-
var expiresName = $"{TokenPrefix}expires_at"; string? refreshToken = null;
105+
var expiresName = $"{TokenPrefix}expires_at";
106106
if (!string.IsNullOrEmpty(parameters.Resource))
107107
{
108108
expiresName += $"::{parameters.Resource}";
109109
}
110110
const string refreshTokenName = $"{TokenPrefix}{OpenIdConnectParameterNames.RefreshToken}";
111111

112+
string? refreshToken = null;
112113
string? accessToken = null;
113114
string? accessTokenType = null;
114115
string? dpopKey = null;
@@ -156,7 +157,7 @@ public async Task StoreTokenAsync(
156157
UserToken token,
157158
UserTokenRequestParameters? parameters = null)
158159
{
159-
parameters ??= new ();
160+
parameters ??= new();
160161

161162
// check the cache in case the cookie was re-issued via StoreTokenAsync
162163
// we use String.Empty as the key for a null SignInScheme
@@ -173,13 +174,7 @@ public async Task StoreTokenAsync(
173174
// in case you want to filter certain claims before re-issuing the authentication session
174175
var transformedPrincipal = await FilterPrincipalAsync(result.Principal!).ConfigureAwait(false);
175176

176-
var expiresName = "expires_at";
177-
if (!string.IsNullOrEmpty(parameters.Resource))
178-
{
179-
expiresName += $"::{parameters.Resource}";
180-
}
181-
182-
var tokenName = OpenIdConnectParameterNames.AccessToken;
177+
var tokenName = $"{TokenPrefix}{OpenIdConnectParameterNames.AccessToken}";
183178
if (!string.IsNullOrEmpty(parameters.Resource))
184179
{
185180
tokenName += $"::{parameters.Resource}";
@@ -194,7 +189,11 @@ public async Task StoreTokenAsync(
194189
{
195190
dpopKeyName += $"::{parameters.Resource}";
196191
}
197-
192+
var expiresName = $"{TokenPrefix}expires_at";
193+
if (!string.IsNullOrEmpty(parameters.Resource))
194+
{
195+
expiresName += $"::{parameters.Resource}";
196+
}
198197
var refreshTokenName = $"{OpenIdConnectParameterNames.RefreshToken}";
199198

200199
if (AppendChallengeSchemeToTokenNames(parameters))
@@ -206,13 +205,13 @@ public async Task StoreTokenAsync(
206205
expiresName += $"||{parameters.ChallengeScheme}";
207206
}
208207

209-
result.Properties!.Items[$"{TokenPrefix}{tokenName}"] = token.AccessToken;
210-
result.Properties!.Items[$"{TokenPrefix}{tokenTypeName}"] = token.AccessTokenType;
208+
result.Properties!.Items[tokenName] = token.AccessToken;
209+
result.Properties!.Items[tokenTypeName] = token.AccessTokenType;
211210
if (token.DPoPJsonWebKey != null)
212211
{
213-
result.Properties!.Items[$"{TokenPrefix}{dpopKeyName}"] = token.DPoPJsonWebKey;
212+
result.Properties!.Items[dpopKeyName] = token.DPoPJsonWebKey;
214213
}
215-
result.Properties!.Items[$"{TokenPrefix}{expiresName}"] = token.Expiration.ToString("o", CultureInfo.InvariantCulture);
214+
result.Properties!.Items[expiresName] = token.Expiration.ToString("o", CultureInfo.InvariantCulture);
216215

217216
if (token.RefreshToken != null)
218217
{

src/Duende.AccessTokenManagement.OpenIdConnect/UserAccessTokenManagementService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,16 @@ public async Task<UserToken> GetAccessTokenAsync(
8383
return userToken;
8484
}
8585

86-
if (userToken.AccessToken.IsMissing() && userToken.RefreshToken.IsPresent())
86+
var needsRenewal = userToken.AccessToken.IsMissing() && userToken.RefreshToken.IsPresent();
87+
if (needsRenewal)
8788
{
8889
_logger.LogDebug(
8990
"No access token found in user token store for user {user} / resource {resource}. Trying to refresh.",
9091
userName, parameters.Resource ?? "default");
9192
}
9293

9394
var dtRefresh = userToken.Expiration.Subtract(_options.RefreshBeforeExpiration);
94-
if (dtRefresh < _clock.UtcNow || parameters.ForceRenewal)
95+
if (dtRefresh < _clock.UtcNow || parameters.ForceRenewal || needsRenewal)
9596
{
9697
_logger.LogDebug("Token for user {user} needs refreshing.", userName);
9798

test/Tests/UserTokenManagementTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public async Task Short_token_lifetime_should_trigger_refresh()
262262
token.ShouldNotBeNull();
263263
token.IsError.ShouldBeFalse();
264264
token.AccessToken.ShouldBe("refreshed2_access_token");
265-
token.AccessTokenType.ShouldBe("token_type");
265+
token.AccessTokenType.ShouldBe("token_type2");
266266
token.RefreshToken.ShouldBe("refreshed2_refresh_token");
267267
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);
268268
}

0 commit comments

Comments
 (0)