Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First draft of DPoP support #1184

Merged
merged 47 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
63c237e
quick commiut to save work
brockallen Feb 13, 2023
5429c4e
add dpop to disco, add dpop validator, update token request validator…
brockallen Feb 14, 2023
09d6b79
more dpop validation and tests
brockallen Feb 15, 2023
d6a9736
more dpop validation and tests
brockallen Feb 15, 2023
134812f
update nonce test
brockallen Feb 15, 2023
c545cbd
move classes out into own files
brockallen Feb 15, 2023
bea586b
fix some test names
brockallen Feb 15, 2023
f0d7816
add client to context, make iat required
brockallen Feb 15, 2023
58a8aa0
first integration tests
brockallen Feb 16, 2023
29f1a2c
more integration tests
brockallen Feb 16, 2023
97eb350
track jkt with refresh tokens, more tests
brockallen Feb 16, 2023
aa11b74
test name change
brockallen Feb 16, 2023
029be60
accept dpop token thumbprint on the authorize endpoint
brockallen Feb 17, 2023
b76de9e
prefer IsPresent helpers
brockallen Feb 17, 2023
e1dc6e7
add dpop flag to EF client entity
brockallen Feb 17, 2023
421b243
emit dpop nonce if validator returns one
brockallen Feb 17, 2023
eb28af1
add a simple DPoP nonce validation strategy
brockallen Feb 23, 2023
77490cf
add options for DPoPTokenValidityDuration
brockallen Feb 23, 2023
05ec8c3
add clock skew to dpop token validation
brockallen Feb 23, 2023
ad9c084
add per-client dpop token expiration validation mode
brockallen Feb 23, 2023
f87ead8
fix bug in test and remove unnecessary validation check
brockallen Feb 23, 2023
0445ebe
add clock skew to replay cache expiration
brockallen Feb 24, 2023
8a10f16
remove timing tests that are coverered elsewhere
brockallen Feb 24, 2023
44860f0
fix flaky integration test that relies upon timing
brockallen Feb 24, 2023
eba796b
reomve unused method
brockallen Feb 24, 2023
5fdba6a
add initial MVC DPoP sample client
brockallen Feb 24, 2023
17c6fb1
add code to create proof token for token renewal w/ automatic access …
brockallen Feb 24, 2023
87401d0
and nonce header to normal token responses
brockallen Feb 27, 2023
ca90d2f
update IdentityModel
brockallen Feb 27, 2023
f773d7d
update for server clock skew
brockallen Feb 27, 2023
86a4335
more updates from review
brockallen Feb 28, 2023
bffa62c
update tests
brockallen Feb 28, 2023
3bf1577
remove more review comments
brockallen Feb 28, 2023
3b17adf
add renew button
brockallen Mar 1, 2023
68c4907
allow confidential clients to use new dpop proof when renewing
brockallen Mar 1, 2023
a19a451
require all clients to use proof token at renewal if they did initially
brockallen Mar 1, 2023
b98fa89
more pop refinement
brockallen Mar 2, 2023
4b7c545
add helpers for jkt
brockallen Mar 3, 2023
ffa5f20
add proof type concept, save on refresh token, and handle legacy refr…
brockallen Mar 3, 2023
d0a3641
small cleanup and issue error when legacy confirmation data is not va…
brockallen Mar 7, 2023
d0b8854
add comment
brockallen Mar 8, 2023
4494135
update MVC DPoP client to send dpop header to API
brockallen Mar 8, 2023
79d78a9
add start of DPoP API host
brockallen Mar 8, 2023
666622a
update IdentityModel for dpop nonce header
brockallen Mar 9, 2023
1c20f70
update client with constants
brockallen Mar 9, 2023
ca9fd67
add comments on how client library might need to be enhanced
brockallen Mar 13, 2023
4e51c7b
remove unused internal interface method
brockallen Mar 13, 2023
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
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<ItemGroup>
<!--our stuff -->
<PackageReference Update="IdentityModel" Version="6.1.0-preview.2"/>
<PackageReference Update="IdentityModel" Version="6.1.0-preview.4"/>

<!--build related-->
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
Expand Down
20 changes: 17 additions & 3 deletions clients/Duende.IdentityServer.Clients.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.352
# Visual Studio Version 17
VisualStudioVersion = 17.4.33122.133
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{187AC294-0420-4726-808B-ED49148C0632}"
EndProject
Expand Down Expand Up @@ -67,7 +67,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleResourceIndicators",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcJarUriJwt", "src\MvcJarUriJwt\MvcJarUriJwt.csproj", "{0BC37D8C-5A67-4A4E-A562-AEBCC97A31D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleCibaClient", "src\ConsoleCibaClient\ConsoleCibaClient.csproj", "{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleCibaClient", "src\ConsoleCibaClient\ConsoleCibaClient.csproj", "{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcDPoP", "src\MvcDPoP\MvcDPoP.csproj", "{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DPoPApi", "src\APIs\DPoPApi\DPoPApi.csproj", "{8F1405C7-CF4D-4780-BDE1-852E743987C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -187,6 +191,14 @@ Global
{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D}.Release|Any CPU.Build.0 = Release|Any CPU
{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD}.Release|Any CPU.Build.0 = Release|Any CPU
{8F1405C7-CF4D-4780-BDE1-852E743987C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F1405C7-CF4D-4780-BDE1-852E743987C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F1405C7-CF4D-4780-BDE1-852E743987C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F1405C7-CF4D-4780-BDE1-852E743987C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -223,6 +235,8 @@ Global
{C07E9414-8AFF-4B71-8B28-76DA6250B94C} = {D027D36B-262B-450A-B444-5B7893B5142E}
{0BC37D8C-5A67-4A4E-A562-AEBCC97A31D9} = {158628D7-8B68-451E-AF22-B64F473C5943}
{5B89FD38-A096-4C9B-B39F-1FD6C591EE3D} = {D027D36B-262B-450A-B444-5B7893B5142E}
{68ACBCA3-3466-4AFB-9986-E8C8D840E0CD} = {158628D7-8B68-451E-AF22-B64F473C5943}
{8F1405C7-CF4D-4780-BDE1-852E743987C6} = {AFE7085F-051E-4829-955F-3426FE643BDD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BAD78470-3D66-466E-9C17-2A67F0905B18}
Expand Down
16 changes: 16 additions & 0 deletions clients/src/APIs/DPoPApi/DPoPApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Constants\Constants.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions clients/src/APIs/DPoPApi/IdentityController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Linq;

namespace DPoPApi.Controllers
{
[Route("identity")]
public class IdentityController : ControllerBase
{
private readonly ILogger<IdentityController> _logger;

public IdentityController(ILogger<IdentityController> logger)
{
_logger = logger;
}

[HttpGet]
public ActionResult Get()
{
var claims = User.Claims.Select(c => new { c.Type, c.Value });
_logger.LogInformation("claims: {claims}", claims);

return new JsonResult(claims);
}
}
}
40 changes: 40 additions & 0 deletions clients/src/APIs/DPoPApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;

namespace DPoPApi
{
public class Program
{
public static void Main(string[] args)
{
Console.Title = "DPoP API";

BuildWebHost(args).Run();
}

public static IHost BuildWebHost(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
.CreateLogger();

return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseSerilog()
.Build();
}
}
}
11 changes: 11 additions & 0 deletions clients/src/APIs/DPoPApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"Api": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5005"
}
}
}
51 changes: 51 additions & 0 deletions clients/src/APIs/DPoPApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.IdentityModel.Tokens.Jwt;
using Clients;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace DPoPApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();

services.AddCors();
services.AddDistributedMemoryCache();

// this API will accept any access token from the authority
services.AddAuthentication("token")
.AddJwtBearer("token", options =>
{
options.Authority = Constants.Authority;
options.TokenValidationParameters.ValidateAudience = false;
options.MapInboundClaims = false;

options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
}

public void Configure(IApplicationBuilder app)
{
app.UseCors(policy =>
{
policy.WithOrigins(
"https://localhost:44300");

policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.WithExposedHeaders("WWW-Authenticate");
});

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireAuthorization();
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net.Http;
using System.Threading.Tasks;
using Clients;
using Microsoft.AspNetCore.Authentication;
using Duende.AccessTokenManagement.OpenIdConnect;

namespace MvcCode.Controllers
{
Expand All @@ -21,6 +23,12 @@ public HomeController(IHttpClientFactory httpClientFactory)

public IActionResult Secure() => View();

public async Task<IActionResult> Renew()
{
await HttpContext.GetUserAccessTokenAsync(new UserTokenRequestParameters { ForceRenewal = true });
return RedirectToAction(nameof(Secure));
}

public IActionResult Logout() => SignOut("oidc");

public async Task<IActionResult> CallApi()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Authentication

<h2>Claims</h2>

<div>
<a asp-action="Renew" class="btn btn-primary">Renew Tokens</a>
</div>

<dl>
@foreach (var claim in User.Claims)
{
Expand Down
46 changes: 46 additions & 0 deletions clients/src/MvcDPoP/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using System.Net.Http;
using System.Threading.Tasks;
using Clients;
using Microsoft.AspNetCore.Authentication;
using Duende.AccessTokenManagement.OpenIdConnect;

namespace MvcDPoP.Controllers
{
public class HomeController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;

public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

[AllowAnonymous]
public IActionResult Index() => View();

public IActionResult Secure() => View();

public async Task<IActionResult> Renew()
{
await HttpContext.GetUserAccessTokenAsync(new UserTokenRequestParameters { ForceRenewal = true });
return RedirectToAction(nameof(Secure));
}

public IActionResult Logout() => SignOut("oidc");

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

var response = await client.GetStringAsync("identity");
ViewBag.Json = response.PrettyPrintJson();

return View();
}


}
}
38 changes: 38 additions & 0 deletions clients/src/MvcDPoP/DPoPOpenIdConnectEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Clients;
using IdentityModel;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using System.Threading.Tasks;

namespace MvcDPoP;

public class DPoPOpenIdConnectEvents : OpenIdConnectEvents
{
public override Task RedirectToIdentityProvider(RedirectContext context)
{
// create the dpop key
var key = DPoPProof.CreateProofKey();

// we store the proof key here to avoid server side and load balancing storage issues
context.Properties.SetProofKey(key);

// pass jkt to authorize endpoint
context.ProtocolMessage.Parameters[OidcConstants.AuthorizeRequest.DPoPKeyThumbprint] = key.CreateJkt();

return base.RedirectToIdentityProvider(context);
}

public override async Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// get key from storage
var key = context.Properties.GetProofKey();

// create proof token for token endpoint
var proofToken = key.CreateProofToken("POST", $"{Constants.Authority}/connect/token");

// set it so the OIDC message handler can find it
context.HttpContext.SetOutboundProofToken(proofToken);

await base.AuthorizationCodeReceived(context);
}

}
29 changes: 29 additions & 0 deletions clients/src/MvcDPoP/DPoPProofApiMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using IdentityModel;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace MvcDPoP;

public class DPoPProofApiMessageHandler : DelegatingHandler
{
private IHttpContextAccessor _http;

public DPoPProofApiMessageHandler(IHttpContextAccessor httpContextAccessor)
{
_http = httpContextAccessor;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var proofKey = await _http.HttpContext?.GetProofKeyAsync();
if (proofKey != null)
{
var proofToken = proofKey.CreateProofToken(request.Method.ToString(), request.RequestUri.ToString());
request.Headers.Add(OidcConstants.HttpHeaders.DPoP, proofToken);
}

return await base.SendAsync(request, cancellationToken);
}
}
Loading