diff --git a/Security.sln b/Security.sln
index 4c24c1d22f..2ed873270c 100644
--- a/Security.sln
+++ b/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
diff --git a/samples/OpenIdConnect.AzureAdSample/AuthPropertiesTokenCache.cs b/samples/OpenIdConnect.AzureAdSample/AuthPropertiesTokenCache.cs
new file mode 100644
index 0000000000..f174174cf8
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/AuthPropertiesTokenCache.cs
@@ -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
+ }
+ }
+}
diff --git a/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.xproj b/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.xproj
new file mode 100644
index 0000000000..c7b0ff10ed
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.xproj
@@ -0,0 +1,23 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 3a7ad414-ebde-4f92-b307-4e8f19b6117e
+ OpenIdConnect.AzureAdSample
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+ 2.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json b/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json
new file mode 100644
index 0000000000..22d7eec72e
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json
@@ -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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/OpenIdConnect.AzureAdSample/Startup.cs b/samples/OpenIdConnect.AzureAdSample/Startup.cs
new file mode 100644
index 0000000000..c5ddafd5ff
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/Startup.cs
@@ -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($"
Signing out {context.User.Identity.Name}
{Environment.NewLine}");
+ await context.Response.WriteAsync("Sign In");
+ await context.Response.WriteAsync($"");
+ 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($"Hello Authenticated User {context.User.Identity.Name}
{Environment.NewLine}");
+ await context.Response.WriteAsync("Claims:
" + Environment.NewLine);
+ foreach (var claim in context.User.Claims)
+ {
+ await context.Response.WriteAsync($"{claim.Type}: {claim.Value}
{Environment.NewLine}");
+ }
+
+ await context.Response.WriteAsync("Tokens:
" + 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}
{Environment.NewLine}");
+ }
+ catch (Exception ex)
+ {
+ await context.Response.WriteAsync($"AquireToken error: {ex.Message}
{Environment.NewLine}");
+ }
+
+ await context.Response.WriteAsync("Sign Out");
+ await context.Response.WriteAsync($"");
+ });
+ }
+
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseDefaultConfiguration(args)
+ .UseServer("Microsoft.AspNetCore.Server.Kestrel")
+ .UseIISPlatformHandlerUrl()
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/samples/OpenIdConnect.AzureAdSample/project.json b/samples/OpenIdConnect.AzureAdSample/project.json
new file mode 100644
index 0000000000..dabb9262fc
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/project.json
@@ -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"
+}
\ No newline at end of file
diff --git a/samples/OpenIdConnect.AzureAdSample/wwwroot/web.config b/samples/OpenIdConnect.AzureAdSample/wwwroot/web.config
new file mode 100644
index 0000000000..8485f6719f
--- /dev/null
+++ b/samples/OpenIdConnect.AzureAdSample/wwwroot/web.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs
index d1737cd259..49c863e4b8 100644
--- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs
+++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs
@@ -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
///
/// Creates a
///
- public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
+ public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options)
{
- Properties = properties;
}
public AuthenticationProperties Properties { get; set; }
///
- /// Gets or sets the 'code'.
- ///
- public string Code { get; set; }
-
- ///
- /// Gets or sets the that was received in the id_token + code OpenIdConnectRequest.
+ /// Gets or sets the that was received in the authentication response, if any.
///
public JwtSecurityToken JwtSecurityToken { get; set; }
///
- /// Gets or sets the 'redirect_uri'.
+ /// The request that will be sent to the token endpoint and is available for customization.
///
- /// This is the redirect_uri that was sent in the id_token + code OpenIdConnectRequest.
- [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "user controlled, not necessarily a URI")]
- public string RedirectUri { get; set; }
+ public OpenIdConnectMessage TokenEndpointRequest { get; set; }
+
+ ///
+ /// The configured communication channel to the identity provider for use when making custom requests to the token endpoint.
+ ///
+ public HttpClient Backchannel { get; internal set; }
+
+ ///
+ /// 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.
+ ///
+ public OpenIdConnectMessage TokenEndpointResponse { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public bool HandledCodeRedemption => TokenEndpointResponse != null;
+
+ ///
+ /// 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.
+ ///
+ public void HandleCodeRedemption()
+ {
+ TokenEndpointResponse = new OpenIdConnectMessage();
+ }
+
+ ///
+ /// 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.
+ ///
+ public void HandleCodeRedemption(string accessToken, string idToken)
+ {
+ TokenEndpointResponse = new OpenIdConnectMessage() { AccessToken = accessToken, IdToken = idToken };
+ }
+
+ ///
+ /// 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.
+ ///
+ public void HandleCodeRedemption(OpenIdConnectMessage tokenEndpointResponse)
+ {
+ TokenEndpointResponse = tokenEndpointResponse;
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs
index 4084e21007..4dc4a32b85 100644
--- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs
+++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs
@@ -303,7 +303,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// Invoked to process incoming OpenIdConnect messages.
///
/// An if successful.
- /// Uses log id's OIDCH-0000 - OIDCH-0025
protected override async Task 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
/// The authorization code to redeem.
/// Uri that was passed in the request sent for the authorization code.
/// OpenIdConnect message that has tokens inside it.
- protected virtual async Task RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
+ protected virtual async Task 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 RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt)
+ private async Task 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);