From b6da2c191c495c1cdf05615e6d3a7ecd61dd54f4 Mon Sep 17 00:00:00 2001 From: Brett Hazen <2651260+bhazen@users.noreply.github.com> Date: Thu, 8 May 2025 12:57:55 -0500 Subject: [PATCH] Return invalid_request when DPoP access token sent without proof --- .../DPoP/DPoPJwtBearerEvents.cs | 4 +- .../DPoPIntegrationTests.cs | 39 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/aspnetcore-authentication-jwtbearer/src/AspNetCore.Authentication.JwtBearer/DPoP/DPoPJwtBearerEvents.cs b/aspnetcore-authentication-jwtbearer/src/AspNetCore.Authentication.JwtBearer/DPoP/DPoPJwtBearerEvents.cs index f0a567ea8..b9b0903fb 100644 --- a/aspnetcore-authentication-jwtbearer/src/AspNetCore.Authentication.JwtBearer/DPoP/DPoPJwtBearerEvents.cs +++ b/aspnetcore-authentication-jwtbearer/src/AspNetCore.Authentication.JwtBearer/DPoP/DPoPJwtBearerEvents.cs @@ -82,7 +82,9 @@ internal class DPoPJwtBearerEvents } else { - throw new InvalidOperationException("Missing DPoP (proof token) HTTP header"); + context.HttpContext.Items["DPoP-Error"] = "invalid_request"; + context.Fail("No DPoP header found"); + return; } var result = await _validator.Validate(new DPoPProofValidationContext diff --git a/aspnetcore-authentication-jwtbearer/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs b/aspnetcore-authentication-jwtbearer/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs index 342860e1c..eec56dad8 100644 --- a/aspnetcore-authentication-jwtbearer/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs +++ b/aspnetcore-authentication-jwtbearer/test/AspNetCore.Authentication.JwtBearer.Tests/DPoPIntegrationTests.cs @@ -44,7 +44,6 @@ public class DPoPIntegrationTests(ITestOutputHelper testOutputHelper) result.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } - [Fact] [Trait("Category", "Integration")] public async Task incorrect_token_type_fails() @@ -91,15 +90,8 @@ public class DPoPIntegrationTests(ITestOutputHelper testOutputHelper) Url = "http://localhost/" }); proof.ShouldNotBeNull(); - var anotherProof = await dpopService.CreateProofTokenAsync(new DPoPProofRequest - { - AccessToken = token.AccessToken, - DPoPJsonWebKey = jwk, - Method = "POST", - Url = "http://localhost/" - }); - anotherProof.ShouldNotBeNull(); api.HttpClient.DefaultRequestHeaders.Add(OidcConstants.HttpHeaders.DPoP, [proof.ProofToken, proof.ProofToken]); + var result = await api.HttpClient.GetAsync("/"); result.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); @@ -151,6 +143,35 @@ public class DPoPIntegrationTests(ITestOutputHelper testOutputHelper) result.StatusCode.ShouldBe(HttpStatusCode.OK); } + [Fact] + [Trait("Category", "Integration")] + public async Task access_token_without_proof_token_should_fail() + { + var identityServer = await CreateIdentityServer(); + identityServer.Clients.Add(DPoPOnlyClient); + var jwk = CreateJwk(); + var api = await CreateDPoPApi(); + + var app = new AppHost(identityServer, api, "client1", testOutputHelper, + configureUserTokenManagementOptions: opt => opt.DPoPJsonWebKey = jwk); + await app.Initialize(); + + // Login and get token for api call + await app.LoginAsync("sub"); + var response = await app.BrowserClient.GetAsync(app.Url("/user_token")); + var token = await response.Content.ReadFromJsonAsync(); + token.ShouldNotBeNull(); + token.AccessToken.ShouldNotBeNull(); + token.DPoPJsonWebKey.ShouldNotBeNull(); + api.HttpClient.SetToken("DPoP", token.AccessToken); + + var result = await api.HttpClient.GetAsync("/"); + + result.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + var error = result.Headers.GetValues(HeaderNames.WWWAuthenticate).FirstOrDefault(); + error.ShouldBe("DPoP error=\"invalid_request\""); + } + [Fact] [Trait("Category", "Integration")] public async Task excessively_large_proof_fails()