-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
94d524b
commit 984d0d7
Showing
32 changed files
with
2,235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"version": "0.2.0", | ||
"compounds": [ | ||
{ | ||
"name": "Run All", | ||
"configurations": ["BFF", "API"], | ||
"presentation": { | ||
"hidden": false, | ||
"group": "", | ||
"order": 1 | ||
} | ||
} | ||
], | ||
"configurations": [ | ||
{ | ||
"name": "API", | ||
"type": "coreclr", | ||
"request": "launch", | ||
"preLaunchTask": "build-api", | ||
"program": "${workspaceFolder}/DPoP.Api/bin/Debug/net6.0/DPoP.Api.dll", | ||
"args": [], | ||
"cwd": "${workspaceFolder}/DPoP.Api", | ||
"env": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
}, | ||
"console": "externalTerminal", | ||
}, | ||
{ | ||
"name": "BFF", | ||
"type": "coreclr", | ||
"request": "launch", | ||
"preLaunchTask": "build-bff", | ||
"program": "${workspaceFolder}/DPoP.Bff/bin/Debug/net6.0/DPoP.Bff.dll", | ||
"args": [], | ||
"cwd": "${workspaceFolder}/DPoP.Bff", | ||
"env": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
}, | ||
"serverReadyAction": { | ||
"action": "openExternally", | ||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)" | ||
}, | ||
"console": "externalTerminal", | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "build", | ||
"type": "process", | ||
"command": "dotnet", | ||
"args": [ | ||
"build", | ||
"${workspaceFolder}/DPoP.sln", | ||
"/property:GenerateFullPaths=true", | ||
"/consoleloggerparameters:NoSummary" | ||
], | ||
"problemMatcher": "$msCompile" | ||
}, | ||
{ | ||
"label": "build-api", | ||
"type": "process", | ||
"command": "dotnet", | ||
"args": [ | ||
"build", | ||
"${workspaceFolder}\\DPoP.Api\\DPoP.Api.csproj", | ||
"/property:GenerateFullPaths=true", | ||
"/consoleloggerparameters:NoSummary" | ||
], | ||
"problemMatcher": "$msCompile" | ||
}, | ||
{ | ||
"label": "build-bff", | ||
"type": "process", | ||
"command": "dotnet", | ||
"args": [ | ||
"build", | ||
"${workspaceFolder}\\DPoP.Bff\\DPoP.Bff.csproj", | ||
|
||
"/property:GenerateFullPaths=true", | ||
"/consoleloggerparameters:NoSummary" | ||
], | ||
"problemMatcher": "$msCompile" | ||
} | ||
] | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="IdentityModel" version="6.1.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" /> | ||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" /> | ||
</ItemGroup> | ||
</Project> |
35 changes: 35 additions & 0 deletions
35
IdentityServer/v6/BFF/DPoP/DPoP.Api/DPoP/ConfigureJwtBearerOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.Extensions.Options; | ||
using System; | ||
|
||
namespace DPoP.Api; | ||
|
||
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions> | ||
{ | ||
private readonly string _configScheme; | ||
|
||
public ConfigureJwtBearerOptions(string configScheme) | ||
{ | ||
_configScheme = configScheme; | ||
} | ||
|
||
public void PostConfigure(string name, JwtBearerOptions options) | ||
{ | ||
if (_configScheme == name) | ||
{ | ||
if (options.EventsType != null && !typeof(DPoPJwtBearerEvents).IsAssignableFrom(options.EventsType)) | ||
{ | ||
throw new Exception("EventsType on JwtBearerOptions must derive from DPoPJwtBearerEvents to work with the DPoP support."); | ||
} | ||
if (options.Events != null && !typeof(DPoPJwtBearerEvents).IsAssignableFrom(options.Events.GetType())) | ||
{ | ||
throw new Exception("Events on JwtBearerOptions must derive from DPoPJwtBearerEvents to work with the DPoP support."); | ||
} | ||
|
||
if (options.Events == null && options.EventsType == null) | ||
{ | ||
options.EventsType = typeof(DPoPJwtBearerEvents); | ||
} | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
IdentityServer/v6/BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using IdentityModel; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.IdentityModel.Tokens; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text.Json; | ||
|
||
namespace DPoP.Api; | ||
|
||
/// <summary> | ||
/// Extensions methods for DPoP | ||
/// </summary> | ||
static class DPoPExtensions | ||
{ | ||
const string DPoPPrefix = OidcConstants.AuthenticationSchemes.AuthorizationHeaderDPoP + " "; | ||
|
||
public static bool IsDPoPAuthorizationScheme(this HttpRequest request) | ||
{ | ||
var authz = request.Headers.Authorization.FirstOrDefault(); | ||
return authz?.StartsWith(DPoPPrefix, System.StringComparison.Ordinal) == true; | ||
} | ||
|
||
public static bool TryGetDPoPAccessToken(this HttpRequest request, out string token) | ||
{ | ||
token = null; | ||
|
||
var authz = request.Headers.Authorization.FirstOrDefault(); | ||
if (authz?.StartsWith(DPoPPrefix, System.StringComparison.Ordinal) == true) | ||
{ | ||
token = authz[DPoPPrefix.Length..].Trim(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
public static string GetAuthorizationScheme(this HttpRequest request) | ||
{ | ||
return request.Headers.Authorization.FirstOrDefault()?.Split(' ', System.StringSplitOptions.RemoveEmptyEntries)[0]; | ||
} | ||
|
||
public static string GetDPoPProofToken(this HttpRequest request) | ||
{ | ||
return request.Headers[OidcConstants.HttpHeaders.DPoP].FirstOrDefault(); | ||
} | ||
|
||
public static string GetDPoPNonce(this AuthenticationProperties props) | ||
{ | ||
if (props.Items.ContainsKey("DPoP-Nonce")) | ||
{ | ||
return props.Items["DPoP-Nonce"] as string; | ||
} | ||
return null; | ||
} | ||
public static void SetDPoPNonce(this AuthenticationProperties props, string nonce) | ||
{ | ||
props.Items["DPoP-Nonce"] = nonce; | ||
} | ||
|
||
/// <summary> | ||
/// Create the value of a thumbprint-based cnf claim | ||
/// </summary> | ||
public static string CreateThumbprintCnf(this JsonWebKey jwk) | ||
{ | ||
var jkt = jwk.CreateThumbprint(); | ||
var values = new Dictionary<string, string> | ||
{ | ||
{ JwtClaimTypes.ConfirmationMethods.JwkThumbprint, jkt } | ||
}; | ||
return JsonSerializer.Serialize(values); | ||
} | ||
|
||
/// <summary> | ||
/// Create the value of a thumbprint | ||
/// </summary> | ||
public static string CreateThumbprint(this JsonWebKey jwk) | ||
{ | ||
var jkt = Base64Url.Encode(jwk.ComputeJwkThumbprint()); | ||
return jkt; | ||
} | ||
} |
152 changes: 152 additions & 0 deletions
152
IdentityServer/v6/BFF/DPoP/DPoP.Api/DPoP/DPoPJwtBearerEvents.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
using IdentityModel; | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Net.Http.Headers; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using static IdentityModel.OidcConstants; | ||
|
||
namespace DPoP.Api; | ||
|
||
public class DPoPJwtBearerEvents : JwtBearerEvents | ||
{ | ||
private readonly IOptionsMonitor<DPoPOptions> _optionsMonitor; | ||
private readonly DPoPProofValidator _validator; | ||
|
||
public DPoPJwtBearerEvents(IOptionsMonitor<DPoPOptions> optionsMonitor, DPoPProofValidator validator) | ||
{ | ||
_optionsMonitor = optionsMonitor; | ||
_validator = validator; | ||
} | ||
|
||
public override Task MessageReceived(MessageReceivedContext context) | ||
{ | ||
var dpopOptions = _optionsMonitor.Get(context.Scheme.Name); | ||
|
||
if (context.HttpContext.Request.TryGetDPoPAccessToken(out var token)) | ||
{ | ||
context.Token = token; | ||
} | ||
else if (dpopOptions.Mode == DPoPMode.DPoPOnly) | ||
{ | ||
// this rejects the attempt for this handler, | ||
// since we don't want to attempt Bearer given the Mode | ||
context.NoResult(); | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
public override async Task TokenValidated(TokenValidatedContext context) | ||
{ | ||
var dpopOptions = _optionsMonitor.Get(context.Scheme.Name); | ||
|
||
if (context.HttpContext.Request.TryGetDPoPAccessToken(out var at)) | ||
{ | ||
var proofToken = context.HttpContext.Request.GetDPoPProofToken(); | ||
var result = await _validator.ValidateAsync(new DPoPProofValidatonContext | ||
{ | ||
Scheme = context.Scheme.Name, | ||
ProofToken = proofToken, | ||
AccessToken = at, | ||
Method = context.HttpContext.Request.Method, | ||
Url = context.HttpContext.Request.Scheme + "://" + context.HttpContext.Request.Host + context.HttpContext.Request.PathBase + context.HttpContext.Request.Path | ||
}); | ||
|
||
if (result.IsError) | ||
{ | ||
// fails the result | ||
context.Fail(result.ErrorDescription ?? result.Error); | ||
|
||
// we need to stash these values away so they are available later when the Challenge method is called later | ||
context.HttpContext.Items["DPoP-Error"] = result.Error; | ||
if (!string.IsNullOrWhiteSpace(result.ErrorDescription)) | ||
{ | ||
context.HttpContext.Items["DPoP-ErrorDescription"] = result.ErrorDescription; | ||
} | ||
if (!string.IsNullOrWhiteSpace(result.ServerIssuedNonce)) | ||
{ | ||
context.HttpContext.Items["DPoP-Nonce"] = result.ServerIssuedNonce; | ||
} | ||
} | ||
} | ||
else if (dpopOptions.Mode == DPoPMode.DPoPAndBearer) | ||
{ | ||
// if the scheme used was not DPoP, then it was Bearer | ||
// and if a access token was presented with a cnf, then the | ||
// client should have sent it as DPoP, so we fail the request | ||
if (context.Principal.HasClaim(x => x.Type == JwtClaimTypes.Confirmation)) | ||
{ | ||
context.HttpContext.Items["Bearer-ErrorDescription"] = "Must use DPoP when using an access token with a 'cnf' claim"; | ||
context.Fail("Must use DPoP when using an access token with a 'cnf' claim"); | ||
} | ||
} | ||
} | ||
|
||
public override Task Challenge(JwtBearerChallengeContext context) | ||
{ | ||
var dpopOptions = _optionsMonitor.Get(context.Scheme.Name); | ||
|
||
if (dpopOptions.Mode == DPoPMode.DPoPOnly) | ||
{ | ||
// if we are using DPoP only, then we don't need/want the default | ||
// JwtBearerHandler to add its WWW-Authenticate response header | ||
// so we have to set the status code ourselves | ||
context.Response.StatusCode = 401; | ||
context.HandleResponse(); | ||
} | ||
else if (context.HttpContext.Items.ContainsKey("Bearer-ErrorDescription")) | ||
{ | ||
var description = context.HttpContext.Items["Bearer-ErrorDescription"] as string; | ||
context.ErrorDescription = description; | ||
} | ||
|
||
if (context.HttpContext.Request.IsDPoPAuthorizationScheme()) | ||
{ | ||
// if we are challening due to dpop, then don't allow bearer www-auth to emit an error | ||
context.Error = null; | ||
} | ||
|
||
// now we always want to add our WWW-Authenticate for DPoP | ||
// For example: | ||
// WWW-Authenticate: DPoP error="invalid_dpop_proof", error_description="Invalid 'iat' value." | ||
var sb = new StringBuilder(); | ||
sb.Append(OidcConstants.AuthenticationSchemes.AuthorizationHeaderDPoP); | ||
|
||
if (context.HttpContext.Items.ContainsKey("DPoP-Error")) | ||
{ | ||
var error = context.HttpContext.Items["DPoP-Error"] as string; | ||
sb.Append(" error=\""); | ||
sb.Append(error); | ||
sb.Append('\"'); | ||
|
||
if (context.HttpContext.Items.ContainsKey("DPoP-ErrorDescription")) | ||
{ | ||
var description = context.HttpContext.Items["DPoP-ErrorDescription"] as string; | ||
|
||
sb.Append(", error_description=\""); | ||
sb.Append(description); | ||
sb.Append('\"'); | ||
} | ||
} | ||
|
||
context.Response.Headers.Add(HeaderNames.WWWAuthenticate, sb.ToString()); | ||
|
||
|
||
if (context.HttpContext.Items.ContainsKey("DPoP-Nonce")) | ||
{ | ||
var nonce = context.HttpContext.Items["DPoP-Nonce"] as string; | ||
context.Response.Headers[HttpHeaders.DPoPNonce] = nonce; | ||
} | ||
else | ||
{ | ||
var nonce = context.Properties.GetDPoPNonce(); | ||
if (nonce != null) | ||
{ | ||
context.Response.Headers[HttpHeaders.DPoPNonce] = nonce; | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace DPoP.Api; | ||
|
||
public enum DPoPMode | ||
{ | ||
/// <summary> | ||
/// Only DPoP tokens will be accepted | ||
/// </summary> | ||
DPoPOnly, | ||
/// <summary> | ||
/// Both DPoP and Bearer tokens will be accepted | ||
/// </summary> | ||
DPoPAndBearer | ||
} |
Oops, something went wrong.