Skip to content

Commit

Permalink
Merge pull request #1184 from DuendeSoftware/brock/dpop
Browse files Browse the repository at this point in the history
First draft of DPoP support
  • Loading branch information
brockallen committed Mar 13, 2023
2 parents 19a3e69 + 4e51c7b commit b05f8fb
Show file tree
Hide file tree
Showing 113 changed files with 43,100 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.targets
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
});
}
}
}
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
@@ -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
@@ -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
@@ -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
@@ -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);
}
}

0 comments on commit b05f8fb

Please sign in to comment.