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);