Skip to content

Commit

Permalink
Merge pull request #90 from DuendeSoftware/joe/dpop-resources-2.1
Browse files Browse the repository at this point in the history
Fix DPoP proof token creation when resources are used
  • Loading branch information
brockallen committed Apr 19, 2024
2 parents db3ac68 + 9b7eaf2 commit a92cadd
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 51 deletions.
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 class AuthenticationSessionUserAccessTokenStore : IUserTokenStore

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 @@ private static string NamePrefixAndResourceSuffix(string type, UserTokenRequestP

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

0 comments on commit a92cadd

Please sign in to comment.