#690 Enable custom OIDC authorization code redemption.
This commit is contained in:
parent
0372daeebf
commit
9bbbe535f2
18
Security.sln
18
Security.sln
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.23107.0
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||
EndProject
|
||||
|
|
@ -56,6 +55,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Owin.Security.Int
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Owin.Security.Interop.Test", "test\Microsoft.Owin.Security.Interop.Test\Microsoft.Owin.Security.Interop.Test.xproj", "{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIdConnect.AzureAdSample", "samples\OpenIdConnect.AzureAdSample\OpenIdConnect.AzureAdSample.xproj", "{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -312,6 +313,18 @@ Global
|
|||
{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -338,5 +351,6 @@ Global
|
|||
{D399B84F-591B-4E98-92BA-B0F63E7B6957} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
{A7922DD8-09F1-43E4-938B-CC523EA08898} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
|
||||
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
|
||||
namespace OpenIdConnect.AzureAdSample
|
||||
{
|
||||
public class AuthPropertiesTokenCache : TokenCache
|
||||
{
|
||||
private const string TokenCacheKey = ".TokenCache";
|
||||
|
||||
private AuthenticationProperties _authProperties;
|
||||
|
||||
public bool HasCacheChanged { get; internal set; }
|
||||
|
||||
public AuthPropertiesTokenCache(AuthenticationProperties authProperties) : base()
|
||||
{
|
||||
_authProperties = authProperties;
|
||||
BeforeAccess = BeforeAccessNotification;
|
||||
AfterAccess = AfterAccessNotification;
|
||||
BeforeWrite = BeforeWriteNotification;
|
||||
|
||||
string cachedTokensText;
|
||||
if (authProperties.Items.TryGetValue(TokenCacheKey, out cachedTokensText))
|
||||
{
|
||||
var cachedTokens = Convert.FromBase64String(cachedTokensText);
|
||||
Deserialize(cachedTokens);
|
||||
}
|
||||
}
|
||||
|
||||
// Notification raised before ADAL accesses the cache.
|
||||
// This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
|
||||
private void BeforeAccessNotification(TokenCacheNotificationArgs args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Notification raised after ADAL accessed the cache.
|
||||
// If the HasStateChanged flag is set, ADAL changed the content of the cache
|
||||
private void AfterAccessNotification(TokenCacheNotificationArgs args)
|
||||
{
|
||||
// if state changed
|
||||
if (HasStateChanged)
|
||||
{
|
||||
HasCacheChanged = true;
|
||||
var cachedTokens = Serialize();
|
||||
var cachedTokensText = Convert.ToBase64String(cachedTokens);
|
||||
_authProperties.Items[TokenCacheKey] = cachedTokensText;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeforeWriteNotification(TokenCacheNotificationArgs args)
|
||||
{
|
||||
// if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>3a7ad414-ebde-4f92-b307-4e8f19b6117e</ProjectGuid>
|
||||
<RootNamespace>OpenIdConnect.AzureAdSample</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DnxInvisibleContent Include="bower.json" />
|
||||
<DnxInvisibleContent Include=".bowerrc" />
|
||||
<DnxInvisibleContent Include="package.json" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:42023",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"Hosting:Environment": "Development"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"commandName": "web",
|
||||
"environmentVariables": {
|
||||
"Hosting:Environment": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace OpenIdConnect.AzureAdSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private const string GraphResourceID = "https://graph.windows.net";
|
||||
|
||||
public Startup()
|
||||
{
|
||||
Configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.AddUserSecrets()
|
||||
.Build();
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAuthentication(sharedOptions =>
|
||||
sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
|
||||
{
|
||||
loggerfactory.AddConsole(LogLevel.Information);
|
||||
|
||||
// Simple error page
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!context.Response.HasStarted)
|
||||
{
|
||||
context.Response.Clear();
|
||||
context.Response.StatusCode = 500;
|
||||
await context.Response.WriteAsync(ex.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.UseIISPlatformHandler();
|
||||
|
||||
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
||||
{
|
||||
AutomaticAuthenticate = true
|
||||
});
|
||||
|
||||
var clientId = Configuration["oidc:clientid"];
|
||||
var clientSecret = Configuration["oidc:clientsecret"];
|
||||
var authority = Configuration["oidc:authority"];
|
||||
var resource = "https://graph.windows.net";
|
||||
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret, // for code flow
|
||||
Authority = authority,
|
||||
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
|
||||
// GetClaimsFromUserInfoEndpoint = true,
|
||||
Events = new OpenIdConnectEvents()
|
||||
{
|
||||
OnAuthorizationCodeReceived = async context =>
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
var currentUri = UriHelper.Encode(request.Scheme, request.Host, request.PathBase, request.Path);
|
||||
var credential = new ClientCredential(clientId, clientSecret);
|
||||
var authContext = new AuthenticationContext(authority, new AuthPropertiesTokenCache(context.Properties));
|
||||
|
||||
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
|
||||
context.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
|
||||
|
||||
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.Request.Path.Equals("/signout"))
|
||||
{
|
||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
context.Response.ContentType = "text/html";
|
||||
await context.Response.WriteAsync($"<html><body>Signing out {context.User.Identity.Name}<br>{Environment.NewLine}");
|
||||
await context.Response.WriteAsync("<a href=\"/\">Sign In</a>");
|
||||
await context.Response.WriteAsync($"</body></html>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
|
||||
{
|
||||
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.ContentType = "text/html";
|
||||
await context.Response.WriteAsync($"<html><body>Hello Authenticated User {context.User.Identity.Name}<br>{Environment.NewLine}");
|
||||
await context.Response.WriteAsync("Claims:<br>" + Environment.NewLine);
|
||||
foreach (var claim in context.User.Claims)
|
||||
{
|
||||
await context.Response.WriteAsync($"{claim.Type}: {claim.Value}<br>{Environment.NewLine}");
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Tokens:<br>" + Environment.NewLine);
|
||||
try
|
||||
{
|
||||
// Retrieve the auth session with the cached tokens
|
||||
var authenticateContext = new AuthenticateContext(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await context.Authentication.AuthenticateAsync(authenticateContext);
|
||||
var authProperties = new AuthenticationProperties(authenticateContext.Properties);
|
||||
var tokenCache = new AuthPropertiesTokenCache(authProperties);
|
||||
|
||||
// Use ADAL to get the right token
|
||||
var authContext = new AuthenticationContext(authority, tokenCache);
|
||||
var credential = new ClientCredential(clientId, clientSecret);
|
||||
string userObjectID = context.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
|
||||
var result = authContext.AcquireTokenSilent(resource, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
|
||||
|
||||
// Update the cookie with the modified tokens
|
||||
if (tokenCache.HasCacheChanged)
|
||||
{
|
||||
await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authenticateContext.Principal, authProperties);
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync($"access_token: {result.AccessToken}<br>{Environment.NewLine}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($"AquireToken error: {ex.Message}<br>{Environment.NewLine}");
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a>");
|
||||
await context.Response.WriteAsync($"</body></html>");
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseDefaultConfiguration(args)
|
||||
.UseServer("Microsoft.AspNetCore.Server.Kestrel")
|
||||
.UseIISPlatformHandlerUrl()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "0.1.0-*",
|
||||
"Microsoft.AspNetCore.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-*",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-*",
|
||||
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
|
||||
"Microsoft.IdentityModel.Clients.ActiveDirectory": "2.22.302111727",
|
||||
"Microsoft.NETCore.Platforms": "1.0.1-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
},
|
||||
"compilationOptions": {
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"commands": {
|
||||
"web": "OpenIdConnect.AzureAdSample"
|
||||
},
|
||||
"userSecretsId": "aspnet5-OpenIdConnectSample-20151210110318"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
|
||||
</handlers>
|
||||
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" startupTimeLimit="3600"/>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||
{
|
||||
|
|
@ -17,29 +18,76 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// Creates a <see cref="AuthorizationCodeReceivedContext"/>
|
||||
/// </summary>
|
||||
public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
|
||||
public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
Properties = properties;
|
||||
}
|
||||
|
||||
public AuthenticationProperties Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'code'.
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="JwtSecurityToken"/> that was received in the id_token + code OpenIdConnectRequest.
|
||||
/// Gets or sets the <see cref="JwtSecurityToken"/> that was received in the authentication response, if any.
|
||||
/// </summary>
|
||||
public JwtSecurityToken JwtSecurityToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// The request that will be sent to the token endpoint and is available for customization.
|
||||
/// </summary>
|
||||
/// <remarks>This is the redirect_uri that was sent in the id_token + code OpenIdConnectRequest.</remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "user controlled, not necessarily a URI")]
|
||||
public string RedirectUri { get; set; }
|
||||
public OpenIdConnectMessage TokenEndpointRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configured communication channel to the identity provider for use when making custom requests to the token endpoint.
|
||||
/// </summary>
|
||||
public HttpClient Backchannel { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the developer chooses to redeem the code themselves then they can provide the resulting tokens here. This is the
|
||||
/// same as calling HandleCodeRedemption. If set then the middleware will not attempt to redeem the code. An IdToken
|
||||
/// is required if one had not been previously received in the authorization response. An access token is optional
|
||||
/// if the middleware is to contact the user-info endpoint.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage TokenEndpointResponse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the developer choose to handle (or skip) the code redemption. If true then the middleware will not attempt
|
||||
/// to redeem the code. See HandleCodeRedemption and TokenEndpointResponse.
|
||||
/// </summary>
|
||||
public bool HandledCodeRedemption => TokenEndpointResponse != null;
|
||||
|
||||
/// <summary>
|
||||
/// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or
|
||||
/// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then
|
||||
/// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received
|
||||
/// in the authorization response. An access token can optionally be provided for the middleware to contact the
|
||||
/// user-info endpoint. Calling this is the same as setting TokenEndpointResponse.
|
||||
/// </summary>
|
||||
public void HandleCodeRedemption()
|
||||
{
|
||||
TokenEndpointResponse = new OpenIdConnectMessage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or
|
||||
/// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then
|
||||
/// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received
|
||||
/// in the authorization response. An access token can optionally be provided for the middleware to contact the
|
||||
/// user-info endpoint. Calling this is the same as setting TokenEndpointResponse.
|
||||
/// </summary>
|
||||
public void HandleCodeRedemption(string accessToken, string idToken)
|
||||
{
|
||||
TokenEndpointResponse = new OpenIdConnectMessage() { AccessToken = accessToken, IdToken = idToken };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or
|
||||
/// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then
|
||||
/// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received
|
||||
/// in the authorization response. An access token can optionally be provided for the middleware to contact the
|
||||
/// user-info endpoint. Calling this is the same as setting TokenEndpointResponse.
|
||||
/// </summary>
|
||||
public void HandleCodeRedemption(OpenIdConnectMessage tokenEndpointResponse)
|
||||
{
|
||||
TokenEndpointResponse = tokenEndpointResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -303,7 +303,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
/// Invoked to process incoming OpenIdConnect messages.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
|
||||
/// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks>
|
||||
protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
|
||||
{
|
||||
Logger.LogTrace(10, "Entering: {0}." + nameof(HandleRemoteAuthenticateAsync), GetType());
|
||||
|
|
@ -450,16 +449,23 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
// Authorization Code or Hybrid flow
|
||||
if (!string.IsNullOrEmpty(authorizationResponse.Code))
|
||||
{
|
||||
// TODO: Does this event provide any value over AuthorizationResponseReceived or AuthorizationResponseValidated?
|
||||
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, properties, ticket, jwt);
|
||||
if (CheckEventResult(authorizationCodeReceivedContext, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
authorizationResponse = authorizationCodeReceivedContext.ProtocolMessage;
|
||||
var code = authorizationCodeReceivedContext.Code;
|
||||
properties = authorizationCodeReceivedContext.Properties;
|
||||
var tokenEndpointRequest = authorizationCodeReceivedContext.TokenEndpointRequest;
|
||||
// If the developer redeemed the code themselves...
|
||||
tokenEndpointResponse = authorizationCodeReceivedContext.TokenEndpointResponse;
|
||||
ticket = authorizationCodeReceivedContext.Ticket;
|
||||
jwt = authorizationCodeReceivedContext.JwtSecurityToken;
|
||||
|
||||
tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);
|
||||
if (!authorizationCodeReceivedContext.HandledCodeRedemption)
|
||||
{
|
||||
tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest);
|
||||
}
|
||||
|
||||
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties);
|
||||
if (CheckEventResult(authorizationCodeRedeemedContext, out result))
|
||||
|
|
@ -485,13 +491,17 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
}
|
||||
}
|
||||
|
||||
Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
|
||||
// Validate the token response if it wasn't provided manually
|
||||
if (!authorizationCodeReceivedContext.HandledCodeRedemption)
|
||||
{
|
||||
ClientId = Options.ClientId,
|
||||
ProtocolMessage = tokenEndpointResponse,
|
||||
ValidatedIdToken = jwt,
|
||||
Nonce = nonce
|
||||
});
|
||||
Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
|
||||
{
|
||||
ClientId = Options.ClientId,
|
||||
ProtocolMessage = tokenEndpointResponse,
|
||||
ValidatedIdToken = jwt,
|
||||
Nonce = nonce
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(authorizationResponse, ticket, properties, tokenEndpointResponse);
|
||||
|
|
@ -574,23 +584,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
/// <param name="authorizationCode">The authorization code to redeem.</param>
|
||||
/// <param name="redirectUri">Uri that was passed in the request sent for the authorization code.</param>
|
||||
/// <returns>OpenIdConnect message that has tokens inside it.</returns>
|
||||
protected virtual async Task<OpenIdConnectMessage> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
|
||||
protected virtual async Task<OpenIdConnectMessage> RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest)
|
||||
{
|
||||
Logger.LogDebug(21, "Redeeming code for tokens.");
|
||||
|
||||
var openIdMessage = new OpenIdConnectMessage()
|
||||
{
|
||||
ClientId = Options.ClientId,
|
||||
ClientSecret = Options.ClientSecret,
|
||||
Code = authorizationCode,
|
||||
GrantType = "authorization_code",
|
||||
RedirectUri = redirectUri
|
||||
};
|
||||
|
||||
// TODO: Event that lets you customize the message. E.g. use certificates, specify resources.
|
||||
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint);
|
||||
requestMessage.Content = new FormUrlEncodedContent(openIdMessage.Parameters);
|
||||
requestMessage.Content = new FormUrlEncodedContent(tokenEndpointRequest.Parameters);
|
||||
var responseMessage = await Backchannel.SendAsync(requestMessage);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
var tokenResonse = await responseMessage.Content.ReadAsStringAsync();
|
||||
|
|
@ -874,19 +872,27 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
return authorizationResponseReceivedContext;
|
||||
}
|
||||
|
||||
private async Task<AuthorizationCodeReceivedContext> RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt)
|
||||
private async Task<AuthorizationCodeReceivedContext> RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage authorizationResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt)
|
||||
{
|
||||
var redirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey];
|
||||
Logger.LogTrace(32, "AuthorizationCode received");
|
||||
|
||||
Logger.LogTrace(32, "AuthorizationCode received: '{0}'", message.Code);
|
||||
|
||||
var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Options, properties)
|
||||
var tokenEndpointRequest = new OpenIdConnectMessage()
|
||||
{
|
||||
Code = message.Code,
|
||||
ProtocolMessage = message,
|
||||
RedirectUri = redirectUri,
|
||||
ClientId = Options.ClientId,
|
||||
ClientSecret = Options.ClientSecret,
|
||||
Code = authorizationResponse.Code,
|
||||
GrantType = OpenIdConnectGrantTypes.AuthorizationCode,
|
||||
RedirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]
|
||||
};
|
||||
|
||||
var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Options)
|
||||
{
|
||||
ProtocolMessage = authorizationResponse,
|
||||
Properties = properties,
|
||||
TokenEndpointRequest = tokenEndpointRequest,
|
||||
Ticket = ticket,
|
||||
JwtSecurityToken = jwt
|
||||
JwtSecurityToken = jwt,
|
||||
Backchannel = Backchannel,
|
||||
};
|
||||
|
||||
await Options.Events.AuthorizationCodeReceived(authorizationCodeReceivedContext);
|
||||
|
|
|
|||
Loading…
Reference in New Issue