-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #147 from DuendeSoftware/joe/par
Add PAR sample
- Loading branch information
Showing
55 changed files
with
39,857 additions
and
9 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
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
6 changes: 3 additions & 3 deletions
6
IdentityServer/v6/Basics/IdentityServer/src/IdentityServerHost.csproj
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
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
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
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,34 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.0.31903.59 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "src\Client.csproj", "{8A7B3FC1-BD45-4679-A3D9-FE2E06F783CF}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServerHost", "..\IdentityServer\src\IdentityServerHost.csproj", "{5F6BD9CA-DC99-4085-88E3-7FDF8D60903E}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleApi", "..\Apis\SimpleApi\SimpleApi.csproj", "{48D8CF98-12BF-4700-8727-61996C498A0A}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{8A7B3FC1-BD45-4679-A3D9-FE2E06F783CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{8A7B3FC1-BD45-4679-A3D9-FE2E06F783CF}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{8A7B3FC1-BD45-4679-A3D9-FE2E06F783CF}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{8A7B3FC1-BD45-4679-A3D9-FE2E06F783CF}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{5F6BD9CA-DC99-4085-88E3-7FDF8D60903E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{5F6BD9CA-DC99-4085-88E3-7FDF8D60903E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{5F6BD9CA-DC99-4085-88E3-7FDF8D60903E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{5F6BD9CA-DC99-4085-88E3-7FDF8D60903E}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{48D8CF98-12BF-4700-8727-61996C498A0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{48D8CF98-12BF-4700-8727-61996C498A0A}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{48D8CF98-12BF-4700-8727-61996C498A0A}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{48D8CF98-12BF-4700-8727-61996C498A0A}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
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,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Duende.AccessTokenManagement.OpenIdConnect" Version="2.0.3"/> | ||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0"/> | ||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
<!-- Constants and helpers --> | ||
<ItemGroup> | ||
<Compile Include="..\..\Shared\Constants.cs"> | ||
<Link>Shared\Constants.cs</Link> | ||
</Compile> | ||
</ItemGroup> | ||
|
||
</Project> |
36 changes: 36 additions & 0 deletions
36
IdentityServer/v6/Basics/MvcPar/src/Controllers/HomeController.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,36 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using System.Text.Json; | ||
|
||
namespace Client.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 IActionResult Logout() => SignOut("oidc", "cookie"); | ||
|
||
public async Task<IActionResult> CallApi() | ||
{ | ||
var client = _httpClientFactory.CreateClient("client"); | ||
|
||
var response = await client.GetStringAsync("identity"); | ||
var json = JsonDocument.Parse(response); | ||
|
||
ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); | ||
return View(); | ||
} | ||
} | ||
} |
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,142 @@ | ||
using System; | ||
using System.Net.Http; | ||
using System.Net.Http.Json; | ||
using System.Text; | ||
using System.Text.Json.Serialization; | ||
using System.Threading.Tasks; | ||
using IdentityModel.Client; | ||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.IdentityModel.Protocols.OpenIdConnect; | ||
|
||
namespace Client | ||
{ | ||
public class ParOidcEvents(HttpClient httpClient, IDiscoveryCache discoveryCache, ILogger<ParOidcEvents> logger) : OpenIdConnectEvents | ||
{ | ||
private readonly HttpClient _httpClient = httpClient; | ||
private readonly IDiscoveryCache _discoveryCache = discoveryCache; | ||
private readonly ILogger<ParOidcEvents> _logger = logger; | ||
|
||
public override async Task RedirectToIdentityProvider(RedirectContext context) | ||
{ | ||
var clientId = context.ProtocolMessage.ClientId; | ||
|
||
// Construct the state parameter and add it to the protocol message | ||
// so that we include it in the pushed authorization request | ||
SetStateParameterForParRequest(context); | ||
|
||
// Make the actual pushed authorization request | ||
var parResponse = await PushAuthorizationParameters(context, clientId); | ||
|
||
// Now replace the parameters that would normally be sent to the | ||
// authorize endpoint with just the client id and PAR request uri. | ||
SetAuthorizeParameters(context, clientId, parResponse); | ||
|
||
// Mark the request as handled, because we don't want the normal | ||
// behavior that attaches state to the outgoing request (we already | ||
// did that in the PAR request). | ||
context.HandleResponse(); | ||
|
||
// Finally redirect to the authorize endpoint | ||
await RedirectToAuthorizeEndpoint(context, context.ProtocolMessage); | ||
} | ||
|
||
private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT"; | ||
private async Task RedirectToAuthorizeEndpoint(RedirectContext context, OpenIdConnectMessage message) | ||
{ | ||
// This code is copied from the ASP.NET handler. We want most of its | ||
// default behavior related to redirecting to the identity provider, | ||
// except we already pushed the state parameter, so that is left out | ||
// here. See https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L364 | ||
if (string.IsNullOrEmpty(message.IssuerAddress)) | ||
{ | ||
throw new InvalidOperationException( | ||
"Cannot redirect to the authorization endpoint, the configuration may be missing or invalid."); | ||
} | ||
|
||
if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) | ||
{ | ||
var redirectUri = message.CreateAuthenticationRequestUrl(); | ||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) | ||
{ | ||
_logger.LogWarning("The redirect URI is not well-formed. The URI is: '{AuthenticationRequestUrl}'.", redirectUri); | ||
} | ||
|
||
context.Response.Redirect(redirectUri); | ||
return; | ||
} | ||
else if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost) | ||
{ | ||
var content = message.BuildFormPost(); | ||
var buffer = Encoding.UTF8.GetBytes(content); | ||
|
||
context.Response.ContentLength = buffer.Length; | ||
context.Response.ContentType = "text/html;charset=UTF-8"; | ||
|
||
// Emit Cache-Control=no-cache to prevent client caching. | ||
context.Response.Headers.CacheControl = "no-cache, no-store"; | ||
context.Response.Headers.Pragma = "no-cache"; | ||
context.Response.Headers.Expires = HeaderValueEpocDate; | ||
|
||
await context.Response.Body.WriteAsync(buffer); | ||
return; | ||
} | ||
|
||
throw new NotImplementedException($"An unsupported authentication method has been configured: {context.Options.AuthenticationMethod}"); | ||
} | ||
|
||
private async Task<ParResponse> PushAuthorizationParameters(RedirectContext context, string clientId) | ||
{ | ||
// Send our PAR request | ||
var requestBody = new FormUrlEncodedContent(context.ProtocolMessage.Parameters); | ||
_httpClient.SetBasicAuthentication(clientId, "secret"); | ||
|
||
var disco = await _discoveryCache.GetAsync(); | ||
if (disco.IsError) | ||
{ | ||
throw new Exception(disco.Error); | ||
} | ||
var parEndpoint = disco.TryGetValue("pushed_authorization_request_endpoint").GetString(); | ||
var response = await _httpClient.PostAsync(parEndpoint, requestBody); | ||
if (!response.IsSuccessStatusCode) | ||
{ | ||
throw new Exception("PAR failure"); | ||
} | ||
return await response.Content.ReadFromJsonAsync<ParResponse>(); | ||
|
||
} | ||
|
||
private static void SetAuthorizeParameters(RedirectContext context, string clientId, ParResponse parResponse) | ||
{ | ||
// Remove all the parameters from the protocol message, and replace with what we got from the PAR response | ||
context.ProtocolMessage.Parameters.Clear(); | ||
// Then, set client id and request uri as parameters | ||
context.ProtocolMessage.ClientId = clientId; | ||
context.ProtocolMessage.RequestUri = parResponse.RequestUri; | ||
} | ||
|
||
private static OpenIdConnectMessage SetStateParameterForParRequest(RedirectContext context) | ||
{ | ||
// Construct State, we also need that (this chunk copied from the OIDC handler) | ||
var message = context.ProtocolMessage; | ||
// When redeeming a code for an AccessToken, this value is needed | ||
context.Properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, message.RedirectUri); | ||
message.State = context.Options.StateDataFormat.Protect(context.Properties); | ||
return message; | ||
} | ||
|
||
public override Task TokenResponseReceived(TokenResponseReceivedContext context) | ||
{ | ||
return base.TokenResponseReceived(context); | ||
} | ||
|
||
private class ParResponse | ||
{ | ||
[JsonPropertyName("expires_in")] | ||
public int ExpiresIn { get; set; } | ||
|
||
[JsonPropertyName("request_uri")] | ||
public string RequestUri { get; set; } | ||
} | ||
} | ||
} |
Oops, something went wrong.