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

Fix DPoP proof token creation when resources are used #90

Merged
merged 4 commits into from
Apr 19, 2024
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
28 changes: 11 additions & 17 deletions samples/Web/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Duende.AccessTokenManagement.OpenIdConnect;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Duende.AccessTokenManagement.OpenIdConnect;

namespace Web.Controllers;

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

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

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

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

return View("CallApi");
}

public async Task<IActionResult> CallApiAsUserFactory()
{
var client = _httpClientFactory.CreateClient("user_client");
var client = _httpClientFactory.CreateClient("user");

var response = await client.GetStringAsync("test");
ViewBag.Json = PrettyPrint(response);
Expand All @@ -72,11 +72,8 @@ public async Task<IActionResult> CallApiAsUserFactoryTyped([FromServices] TypedU
[AllowAnonymous]
public async Task<IActionResult> CallApiAsUserResourceIndicator()
{
var token = await HttpContext.GetUserAccessTokenAsync(new UserTokenRequestParameters { Resource = "urn:resource1" });
var client = _httpClientFactory.CreateClient();
client.SetToken(token.AccessTokenType!, token.AccessToken!);

var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
var client = _httpClientFactory.CreateClient("user-resource");
var response = await client.GetStringAsync("test");

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

var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}test");

ViewBag.Json = PrettyPrint(response);
return View("CallApi");
Expand All @@ -99,11 +96,8 @@ public async Task<IActionResult> CallApiAsClientExtensionMethod()
[AllowAnonymous]
public async Task<IActionResult> CallApiAsClientResourceIndicator()
{
var token = await HttpContext.GetClientAccessTokenAsync(new UserTokenRequestParameters { Resource = "urn:resource1" });
var client = _httpClientFactory.CreateClient();
client.SetToken(token.AccessTokenType!, token.AccessToken!);

var response = await client.GetStringAsync($"{Startup.ApiBaseUrl}/test");
var client = _httpClientFactory.CreateClient("client-resource");
var response = await client.GetStringAsync("test");

ViewBag.Json = PrettyPrint(response);
return View("CallApi");
Expand Down
45 changes: 32 additions & 13 deletions samples/Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks;
using Duende.AccessTokenManagement.OpenIdConnect;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -16,11 +17,14 @@ namespace Web;

public static class Startup
{
public const bool UseDPoP = false;
public const bool UseDPoP = true;

public const string BaseUrl = "https://localhost:5001";
//public const string BaseUrl = "https://demo.duendesoftware.com";

public const string ApiBaseUrl = UseDPoP ?
"https://demo.duendesoftware.com/api/dpop/" :
"https://demo.duendesoftware.com/api/";
$"{BaseUrl}/api/dpop/" :
$"{BaseUrl}/api/";

internal static WebApplication ConfigureServices(this WebApplicationBuilder builder)
{
Expand All @@ -39,8 +43,7 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://demo.duendesoftware.com";
//options.Authority = "https://localhost:5001";
options.Authority = BaseUrl;

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

options.Resource = "urn:resource1";

options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.MapInboundClaims = false;
Expand All @@ -65,12 +70,6 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
NameClaimType = "name",
RoleClaimType = "role"
};

options.Events.OnRedirectToIdentityProvider = ctx =>
{
ctx.ProtocolMessage.Resource = "urn:resource1";
return Task.CompletedTask;
};
});

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

builder.Services.AddOpenIdConnectAccessTokenManagement(options =>
{
options.DPoPJsonWebKey = UseDPoP ? jwk : null; ;
options.DPoPJsonWebKey = UseDPoP ? jwk : null;
});

// registers HTTP client that uses the managed user access token
builder.Services.AddUserAccessTokenHttpClient("user_client",
builder.Services.AddUserAccessTokenHttpClient("user",
configureClient: client => {
client.BaseAddress = new Uri(ApiBaseUrl);
});

// registers HTTP client that uses the managed user access token and
// includes a resource indicator
builder.Services.AddUserAccessTokenHttpClient("user-resource",
new UserTokenRequestParameters
{
Resource = "urn:resource1"
},
configureClient: client => {
client.BaseAddress = new Uri(ApiBaseUrl);
});
Expand All @@ -93,6 +103,15 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil
builder.Services.AddClientAccessTokenHttpClient("client",
configureClient: client => { client.BaseAddress = new Uri(ApiBaseUrl); });

// registers HTTP client that uses the managed client access token and
// includes a resource indicator
builder.Services.AddClientAccessTokenHttpClient("client-resource",
new UserTokenRequestParameters
{
Resource = "urn:resource1"
},
configureClient: client => { client.BaseAddress = new Uri(ApiBaseUrl); });

// registers a typed HTTP client with token management support
builder.Services.AddHttpClient<TypedUserClient>(client =>
{
Expand Down
7 changes: 2 additions & 5 deletions samples/Web/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,5 @@
<a asp-controller="Home" asp-action="CallApiAsClientFactory">HTTP client factory</a>
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientFactoryTyped">HTTP client factory (typed)</a>
@if (!Startup.UseDPoP)
{
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
}
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
14 changes: 4 additions & 10 deletions samples/Web/Views/Home/Secure.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
<a asp-controller="Home" asp-action="CallApiAsUserFactory">HTTP client factory</a>
@("|")
<a asp-controller="Home" asp-action="CallApiAsUserFactoryTyped">HTTP client factory (typed)</a>
@if (!Startup.UseDPoP)
{
@("|")
<a asp-controller="Home" asp-action="CallApiAsUserResourceIndicator">Use resource indicator</a>
}
@("|")
<a asp-controller="Home" asp-action="CallApiAsUserResourceIndicator">Use resource indicator</a>

<h3>Call API as Client</h3>

Expand All @@ -28,11 +25,8 @@
<a asp-controller="Home" asp-action="CallApiAsClientFactory">HTTP client factory</a>
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientFactoryTyped">HTTP client factory (typed)</a>
@if (!Startup.UseDPoP)
{
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>
}
@("|")
<a asp-controller="Home" asp-action="CallApiAsClientResourceIndicator">Use resource indicator</a>

<h2>Claims</h2>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ public async Task<UserToken> GetTokenAsync(

var tokenName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.AccessToken, parameters);
var tokenTypeName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.TokenType, parameters);
var dpopKeyName = NamePrefixAndResourceSuffix(DPoPKeyName, parameters);
var expiresName = NamePrefixAndResourceSuffix("expires_at", parameters);

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

var appendChallengeScheme = AppendChallengeSchemeToTokenNames(parameters);

Expand Down Expand Up @@ -189,12 +190,12 @@ public async Task StoreTokenAsync(

var tokenName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.AccessToken, parameters);
var tokenTypeName = NamePrefixAndResourceSuffix(OpenIdConnectParameterNames.TokenType, parameters);
var dpopKeyName = NamePrefixAndResourceSuffix(DPoPKeyName, parameters);
var expiresName = NamePrefixAndResourceSuffix("expires_at", parameters);

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

if (AppendChallengeSchemeToTokenNames(parameters))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Duende.AccessTokenManagement;
using Duende.AccessTokenManagement.OpenIdConnect;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Authentication;

Expand Down
2 changes: 1 addition & 1 deletion src/Duende.AccessTokenManagement/AccessTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ protected virtual async Task SetTokenAsync(HttpRequestMessage request, bool forc
}

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