From ff9f145a8e89c9756ea12ff10c6d47f2f7eb345f Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 29 Jun 2017 16:27:03 -0700 Subject: [PATCH] Refactor Events + Add IAuthenticationBuilder --- samples/CookieSample/Startup.cs | 4 +- samples/CookieSessionSample/Startup.cs | 4 +- samples/JwtBearerSample/Startup.cs | 6 +- .../OpenIdConnect.AzureAdSample/Startup.cs | 8 +- samples/OpenIdConnectSample/Startup.cs | 8 +- samples/SocialSample/Startup.cs | 83 ++- .../CookieAuthenticationHandler.cs | 40 +- .../CookieExtensions.cs | 24 +- .../Events/BaseCookieContext.cs | 30 -- .../Events/CookieAuthenticationEvents.cs | 16 +- .../Events/CookieSignedInContext.cs | 15 +- .../Events/CookieSigningInContext.cs | 14 +- .../Events/CookieSigningOutContext.cs | 8 +- .../Events/CookieValidatePrincipalContext.cs | 30 +- .../LoggingExtensions.cs | 35 ++ ...stConfigureCookieAuthenticationOptions.cs} | 1 - .../breakingchanges.netcore.json | 80 +-- .../FacebookExtensions.cs | 12 + .../FacebookHandler.cs | 6 +- .../GoogleExtensions.cs | 13 + .../GoogleHandler.cs | 7 +- .../Events/AuthenticationFailedContext.cs | 11 +- .../Events/BaseJwtBearerContext.cs | 32 -- .../Events/JwtBearerChallengeContext.cs | 25 +- .../Events/MessageReceivedContext.cs | 11 +- .../Events/TokenValidatedContext.cs | 11 +- .../JwtBearerExtensions.cs | 15 + .../JwtBearerHandler.cs | 46 +- .../breakingchanges.netcore.json | 27 +- .../MicrosoftAccountExtensions.cs | 13 + .../MicrosoftAccountHandler.cs | 5 +- .../Events/OAuthCreatingTicketContext.cs | 61 +-- .../Events/OAuthEvents.cs | 6 +- .../OAuthRedirectToAuthorizationContext.cs | 41 -- .../OAuthExtensions.cs | 13 + .../OAuthHandler.cs | 27 +- .../breakingchanges.netcore.json | 19 +- .../Events/AuthenticationFailedContext.cs | 11 +- .../AuthorizationCodeReceivedContext.cs | 16 +- .../Events/BaseOpenIdConnectContext.cs | 25 - .../Events/MessageReceivedContext.cs | 17 +- .../Events/RedirectContext.cs | 26 +- .../Events/RemoteSignoutContext.cs | 16 +- .../Events/TokenResponseReceivedContext.cs | 13 +- .../Events/TokenValidatedContext.cs | 17 +- .../Events/UserInformationReceivedContext.cs | 13 +- .../LoggingExtensions.cs | 30 +- .../OpenIdConnectExtensions.cs | 14 + .../OpenIdConnectHandler.cs | 314 ++++++------ .../OpenIdConnectOptions.cs | 5 - .../breakingchanges.netcore.json | 128 ++--- .../Events/BaseTwitterContext.cs | 30 -- .../Events/TwitterCreatingTicketContext.cs | 15 +- .../Events/TwitterEvents.cs | 4 +- ...rRedirectToAuthorizationEndpointContext.cs | 35 -- .../TwitterExtensions.cs | 14 + .../TwitterHandler.cs | 25 +- .../breakingchanges.netcore.json | 33 +- .../AuthenticationBuilder.cs | 103 ++++ .../AuthenticationHandler.cs | 29 -- ...thenticationServiceCollectionExtensions.cs | 11 +- .../Events/BaseAuthenticationContext.cs | 41 -- .../Events/BaseContext.cs | 38 +- .../Events/BaseControlContext.cs | 78 --- .../Events/EventResultState.cs | 23 - .../Events/HandleRequestContext.cs | 32 ++ .../Events/PrincipalContext.cs | 30 ++ .../Events/PropertiesContext.cs | 31 ++ .../Events/RedirectContext.cs} | 18 +- .../Events/RemoteAuthenticationContext.cs | 49 ++ .../Events/RemoteAuthenticationEvents.cs | 4 +- ...lureContext.cs => RemoteFailureContext.cs} | 10 +- .../Events/ResultContext.cs | 65 +++ .../Events/TicketReceivedContext.cs | 25 +- .../LoggingExtensions.cs | 60 --- .../RemoteAuthenticationHandler.cs | 73 ++- .../RemoteAuthenticationOptions.cs | 5 - .../RemoteAuthenticationResult.cs | 78 +++ .../breakingchanges.netcore.json | 46 +- .../PolicyEvaluator.cs | 4 +- .../CookieTests.cs | 45 +- .../FacebookTests.cs | 35 +- .../GoogleTests.cs | 26 +- .../JwtBearerTests.cs | 83 ++- .../MicrosoftAccountTests.cs | 14 +- .../OAuthTests.cs | 23 - .../OpenIdConnectChallengeTests.cs | 4 +- .../OpenIdConnectConfigurationTests.cs | 23 +- .../OpenIdConnect/OpenIdConnectEventTests.cs | 484 ++++++++++++++++-- .../OpenIdConnect/OpenIdConnectTests.cs | 4 - .../OpenIdConnect/TestServerBuilder.cs | 6 +- .../TokenExtensionTests.cs | 6 +- .../TwitterTests.cs | 13 +- .../CookiePolicyTests.cs | 4 +- .../CookieInteropTests.cs | 8 +- 95 files changed, 1746 insertions(+), 1493 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Authentication.Cookies/Events/BaseCookieContext.cs create mode 100644 src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs rename src/Microsoft.AspNetCore.Authentication.Cookies/{CookieAuthenticationPostConfigureOptions.cs => PostConfigureCookieAuthenticationOptions.cs} (99%) delete mode 100644 src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/BaseJwtBearerContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthRedirectToAuthorizationContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/BaseOpenIdConnectContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication.Twitter/Events/BaseTwitterContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterRedirectToAuthorizationEndpointContext.cs create mode 100644 src/Microsoft.AspNetCore.Authentication/AuthenticationBuilder.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication/Events/BaseAuthenticationContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication/Events/EventResultState.cs create mode 100644 src/Microsoft.AspNetCore.Authentication/Events/HandleRequestContext.cs create mode 100644 src/Microsoft.AspNetCore.Authentication/Events/PrincipalContext.cs create mode 100644 src/Microsoft.AspNetCore.Authentication/Events/PropertiesContext.cs rename src/{Microsoft.AspNetCore.Authentication.Cookies/Events/CookieRedirectContext.cs => Microsoft.AspNetCore.Authentication/Events/RedirectContext.cs} (62%) create mode 100644 src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationContext.cs rename src/Microsoft.AspNetCore.Authentication/Events/{FailureContext.cs => RemoteFailureContext.cs} (64%) create mode 100644 src/Microsoft.AspNetCore.Authentication/Events/ResultContext.cs create mode 100644 src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationResult.cs diff --git a/samples/CookieSample/Startup.cs b/samples/CookieSample/Startup.cs index 79d1b3c3fe..a91791070a 100644 --- a/samples/CookieSample/Startup.cs +++ b/samples/CookieSample/Startup.cs @@ -18,9 +18,7 @@ namespace CookieSample { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); - - services.AddCookieAuthentication(); + }).AddCookie(); } public void Configure(IApplicationBuilder app) diff --git a/samples/CookieSessionSample/Startup.cs b/samples/CookieSessionSample/Startup.cs index c35dfd9998..f7b8f2bb88 100644 --- a/samples/CookieSessionSample/Startup.cs +++ b/samples/CookieSessionSample/Startup.cs @@ -19,9 +19,7 @@ namespace CookieSessionSample { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); - - services.AddCookieAuthentication(o => o.SessionStore = new MemoryCacheTicketStore()); + }).AddCookie(o => o.SessionStore = new MemoryCacheTicketStore()); } public void Configure(IApplicationBuilder app) diff --git a/samples/JwtBearerSample/Startup.cs b/samples/JwtBearerSample/Startup.cs index 030e640c99..6f2c5e2ecd 100644 --- a/samples/JwtBearerSample/Startup.cs +++ b/samples/JwtBearerSample/Startup.cs @@ -48,9 +48,7 @@ namespace JwtBearerSample { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }); - - services.AddJwtBearerAuthentication(o => + }).AddJwtBearer(o => { // You also need to update /wwwroot/app/scripts/app.js o.Authority = Configuration["jwt:authority"]; @@ -59,7 +57,7 @@ namespace JwtBearerSample { OnAuthenticationFailed = c => { - c.HandleResponse(); + c.NoResult(); c.Response.StatusCode = 500; c.Response.ContentType = "text/plain"; diff --git a/samples/OpenIdConnect.AzureAdSample/Startup.cs b/samples/OpenIdConnect.AzureAdSample/Startup.cs index e6ad7804d3..aab6e60df8 100644 --- a/samples/OpenIdConnect.AzureAdSample/Startup.cs +++ b/samples/OpenIdConnect.AzureAdSample/Startup.cs @@ -48,11 +48,9 @@ namespace OpenIdConnect.AzureAdSample sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }); - - services.AddCookieAuthentication(); - - services.AddOpenIdConnectAuthentication(o => + }) + .AddCookie() + .AddOpenIdConnect(o => { o.ClientId = ClientId; o.ClientSecret = ClientSecret; // for code flow diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs index af1406289d..bc5af750d3 100644 --- a/samples/OpenIdConnectSample/Startup.cs +++ b/samples/OpenIdConnectSample/Startup.cs @@ -45,11 +45,9 @@ namespace OpenIdConnectSample sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }); - - services.AddCookieAuthentication(); - - services.AddOpenIdConnectAuthentication(o => + }) + .AddCookie() + .AddOpenIdConnect(o => { o.ClientId = Configuration["oidc:clientid"]; o.ClientSecret = Configuration["oidc:clientsecret"]; // for code flow diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs index 0039720096..dcf76263d5 100644 --- a/samples/SocialSample/Startup.cs +++ b/samples/SocialSample/Startup.cs @@ -55,13 +55,11 @@ namespace SocialSample options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); - - services.AddCookieAuthentication(o => o.LoginPath = new PathString("/login")); - - // You must first create an app with Facebook and add its ID and Secret to your user-secrets. - // https://developers.facebook.com/apps/ - services.AddFacebookAuthentication(o => + }) + .AddCookie(o => o.LoginPath = new PathString("/login")) + // You must first create an app with Facebook and add its ID and Secret to your user-secrets. + // https://developers.facebook.com/apps/ + .AddFacebook(o => { o.AppId = Configuration["facebook:appid"]; o.AppSecret = Configuration["facebook:appsecret"]; @@ -69,11 +67,10 @@ namespace SocialSample o.Fields.Add("name"); o.Fields.Add("email"); o.SaveTokens = true; - }); - - // You must first create an app with Google and add its ID and Secret to your user-secrets. - // https://console.developers.google.com/project - services.AddOAuthAuthentication("Google-AccessToken", o => + }) + // You must first create an app with Google and add its ID and Secret to your user-secrets. + // https://console.developers.google.com/project + .AddOAuth("Google-AccessToken", o => { o.ClientId = Configuration["google:clientid"]; o.ClientSecret = Configuration["google:clientsecret"]; @@ -84,11 +81,10 @@ namespace SocialSample o.Scope.Add("profile"); o.Scope.Add("email"); o.SaveTokens = true; - }); - - // You must first create an app with Google and add its ID and Secret to your user-secrets. - // https://console.developers.google.com/project - services.AddGoogleAuthentication(o => + }) + // You must first create an app with Google and add its ID and Secret to your user-secrets. + // https://console.developers.google.com/project + .AddGoogle(o => { o.ClientId = Configuration["google:clientid"]; o.ClientSecret = Configuration["google:clientsecret"]; @@ -104,11 +100,10 @@ namespace SocialSample }; o.ClaimActions.MapJsonSubKey("urn:google:image", "image", "url"); o.ClaimActions.Remove(ClaimTypes.GivenName); - }); - - // You must first create an app with Twitter and add its key and Secret to your user-secrets. - // https://apps.twitter.com/ - services.AddTwitterAuthentication(o => + }) + // You must first create an app with Twitter and add its key and Secret to your user-secrets. + // https://apps.twitter.com/ + .AddTwitter(o => { o.ConsumerKey = Configuration["twitter:consumerkey"]; o.ConsumerSecret = Configuration["twitter:consumersecret"]; @@ -126,15 +121,14 @@ namespace SocialSample return Task.FromResult(0); } }; - }); - - /* Azure AD app model v2 has restrictions that prevent the use of plain HTTP for redirect URLs. - Therefore, to authenticate through microsoft accounts, tryout the sample using the following URL: - https://localhost:44318/ - */ - // You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets. - // https://apps.dev.microsoft.com/ - services.AddOAuthAuthentication("Microsoft-AccessToken", o => + }) + /* Azure AD app model v2 has restrictions that prevent the use of plain HTTP for redirect URLs. + Therefore, to authenticate through microsoft accounts, tryout the sample using the following URL: + https://localhost:44318/ + */ + // You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets. + // https://apps.dev.microsoft.com/ + .AddOAuth("Microsoft-AccessToken", o => { o.ClientId = Configuration["microsoftaccount:clientid"]; o.ClientSecret = Configuration["microsoftaccount:clientsecret"]; @@ -143,20 +137,18 @@ namespace SocialSample o.TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint; o.Scope.Add("https://graph.microsoft.com/user.read"); o.SaveTokens = true; - }); - - // You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets. - // https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-app-registration/ - services.AddMicrosoftAccountAuthentication(o => + }) + // You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets. + // https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-app-registration/ + .AddMicrosoftAccount(o => { o.ClientId = Configuration["microsoftaccount:clientid"]; o.ClientSecret = Configuration["microsoftaccount:clientsecret"]; o.SaveTokens = true; - }); - - // You must first create an app with GitHub and add its ID and Secret to your user-secrets. - // https://github.com/settings/applications/ - services.AddOAuthAuthentication("GitHub-AccessToken", o => + }) + // You must first create an app with GitHub and add its ID and Secret to your user-secrets. + // https://github.com/settings/applications/ + .AddOAuth("GitHub-AccessToken", o => { o.ClientId = Configuration["github-token:clientid"]; o.ClientSecret = Configuration["github-token:clientsecret"]; @@ -164,11 +156,10 @@ namespace SocialSample o.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; o.TokenEndpoint = "https://github.com/login/oauth/access_token"; o.SaveTokens = true; - }); - - // You must first create an app with GitHub and add its ID and Secret to your user-secrets. - // https://github.com/settings/applications/ - services.AddOAuthAuthentication("GitHub", o => + }) + // You must first create an app with GitHub and add its ID and Secret to your user-secrets. + // https://github.com/settings/applications/ + .AddOAuth("GitHub", o => { o.ClientId = Configuration["github:clientid"]; o.ClientSecret = Configuration["github:clientsecret"]; diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs index 9b04026cd1..4751c6f857 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -14,7 +14,10 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Authentication.Cookies { - public class CookieAuthenticationHandler : AuthenticationHandler + public class CookieAuthenticationHandler : + AuthenticationHandler, + IAuthenticationSignInHandler, + IAuthenticationSignOutHandler { private const string HeaderValueNoCache = "no-cache"; private const string HeaderValueMinusOne = "-1"; @@ -104,7 +107,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); if (string.IsNullOrEmpty(cookie)) { - return AuthenticateResult.None(); + return AuthenticateResult.NoResult(); } var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding()); @@ -155,7 +158,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies return result; } - var context = new CookieValidatePrincipalContext(Context, Scheme, result.Ticket, Options); + var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket); await Events.ValidatePrincipal(context); if (context.Principal == null) @@ -244,8 +247,15 @@ namespace Microsoft.AspNetCore.Authentication.Cookies } } - protected override async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) + public async virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + properties = properties ?? new AuthenticationProperties(); + _signInCalled = true; // Process the request cookie to initialize members like _sessionKey. @@ -284,7 +294,8 @@ namespace Microsoft.AspNetCore.Authentication.Cookies signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime(); } - var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme); + var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name); + if (Options.SessionStore != null) { if (_sessionKey != null) @@ -310,20 +321,23 @@ namespace Microsoft.AspNetCore.Authentication.Cookies var signedInContext = new CookieSignedInContext( Context, Scheme, - Options, - Scheme.Name, signInContext.Principal, - signInContext.Properties); + signInContext.Properties, + Options); await Events.SignedIn(signedInContext); // Only redirect on the login path var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath; await ApplyHeaders(shouldRedirect, signedInContext.Properties); + + Logger.SignedIn(Scheme.Name); } - protected override async Task HandleSignOutAsync(AuthenticationProperties properties) + public async virtual Task SignOutAsync(AuthenticationProperties properties) { + properties = properties ?? new AuthenticationProperties(); + _signOutCalled = true; // Process the request cookie to initialize members like _sessionKey. @@ -351,6 +365,8 @@ namespace Microsoft.AspNetCore.Authentication.Cookies // Only redirect on the logout path var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath; await ApplyHeaders(shouldRedirect, context.Properties); + + Logger.SignedOut(Scheme.Name); } private async Task ApplyHeaders(bool shouldRedirectToReturnUrl, AuthenticationProperties properties) @@ -380,7 +396,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies if (redirectUri != null) { await Events.RedirectToReturnUrl( - new CookieRedirectContext(Context, Scheme, Options, redirectUri, properties)); + new RedirectContext(Context, Scheme, Options, properties, redirectUri)); } } } @@ -406,7 +422,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies returnUrl = OriginalPathBase + Request.Path + Request.QueryString; } var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl); - var redirectContext = new CookieRedirectContext(Context, Scheme, Options, BuildRedirectUri(accessDeniedUri), properties); + var redirectContext = new RedirectContext(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri)); await Events.RedirectToAccessDenied(redirectContext); } @@ -419,7 +435,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies } var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri); - var redirectContext = new CookieRedirectContext(Context, Scheme, Options, BuildRedirectUri(loginUri), properties); + var redirectContext = new RedirectContext(Context, Scheme, Options, properties, BuildRedirectUri(loginUri)); await Events.RedirectToLogin(redirectContext); } diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieExtensions.cs index 6f23abcc4a..67c4416ebb 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieExtensions.cs @@ -2,16 +2,32 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { public static class CookieExtensions { + public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder) + => builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme); + + public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme) + => builder.AddCookie(authenticationScheme, configureOptions: null); + + public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>()); + return builder.AddScheme(authenticationScheme, configureOptions); + } + + + // REMOVE below once callers have been updated public static IServiceCollection AddCookieAuthentication(this IServiceCollection services) => services.AddCookieAuthentication(CookieAuthenticationDefaults.AuthenticationScheme); public static IServiceCollection AddCookieAuthentication(this IServiceCollection services, string authenticationScheme) => services.AddCookieAuthentication(authenticationScheme, configureOptions: null); diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/BaseCookieContext.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/BaseCookieContext.cs deleted file mode 100644 index 4c949bb089..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/BaseCookieContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.Cookies -{ - public class BaseCookieContext : BaseAuthenticationContext - { - public BaseCookieContext( - HttpContext context, - AuthenticationScheme scheme, - CookieAuthenticationOptions options, - AuthenticationProperties properties) - : base(context, scheme.Name, properties) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - Options = options; - } - - public CookieAuthenticationOptions Options { get; } - - public AuthenticationScheme Scheme { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs index c7c375de23..2b8b0416b3 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// A delegate assigned to this property will be invoked when the related method is called. /// - public Func OnRedirectToLogin { get; set; } = context => + public Func, Task> OnRedirectToLogin { get; set; } = context => { if (IsAjaxRequest(context.Request)) { @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// A delegate assigned to this property will be invoked when the related method is called. /// - public Func OnRedirectToAccessDenied { get; set; } = context => + public Func, Task> OnRedirectToAccessDenied { get; set; } = context => { if (IsAjaxRequest(context.Request)) { @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// A delegate assigned to this property will be invoked when the related method is called. /// - public Func OnRedirectToLogout { get; set; } = context => + public Func, Task> OnRedirectToLogout { get; set; } = context => { if (IsAjaxRequest(context.Request)) { @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// A delegate assigned to this property will be invoked when the related method is called. /// - public Func OnRedirectToReturnUrl { get; set; } = context => + public Func, Task> OnRedirectToReturnUrl { get; set; } = context => { if (IsAjaxRequest(context.Request)) { @@ -135,24 +135,24 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToLogout(CookieRedirectContext context) => OnRedirectToLogout(context); + public virtual Task RedirectToLogout(RedirectContext context) => OnRedirectToLogout(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToLogin(CookieRedirectContext context) => OnRedirectToLogin(context); + public virtual Task RedirectToLogin(RedirectContext context) => OnRedirectToLogin(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToReturnUrl(CookieRedirectContext context) => OnRedirectToReturnUrl(context); + public virtual Task RedirectToReturnUrl(RedirectContext context) => OnRedirectToReturnUrl(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirectToAccessDenied(context); + public virtual Task RedirectToAccessDenied(RedirectContext context) => OnRedirectToAccessDenied(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSignedInContext.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSignedInContext.cs index 0e610c8b2d..98c31dd190 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSignedInContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSignedInContext.cs @@ -9,32 +9,25 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// Context object passed to the ICookieAuthenticationEvents method SignedIn. /// - public class CookieSignedInContext : BaseCookieContext + public class CookieSignedInContext : PrincipalContext { /// /// Creates a new instance of the context object. /// /// The HTTP request context /// The scheme data - /// The handler options - /// Initializes AuthenticationScheme property /// Initializes Principal property /// Initializes Properties property + /// The handler options public CookieSignedInContext( HttpContext context, AuthenticationScheme scheme, - CookieAuthenticationOptions options, - string authenticationScheme, ClaimsPrincipal principal, - AuthenticationProperties properties) + AuthenticationProperties properties, + CookieAuthenticationOptions options) : base(context, scheme, options, properties) { Principal = principal; } - - /// - /// Contains the claims that were converted into the outgoing cookie. - /// - public ClaimsPrincipal Principal { get; } } } diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningInContext.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningInContext.cs index b91cb7e184..41d7b4f6ae 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningInContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningInContext.cs @@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.Cookies { /// - /// Context object passed to the ICookieAuthenticationEvents method SigningIn. + /// Context object passed to the . /// - public class CookieSigningInContext : BaseCookieContext + public class CookieSigningInContext : PrincipalContext { /// /// Creates a new instance of the context object. @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// The scheme data /// The handler options /// Initializes Principal property - /// Initializes Extra property + /// The authentication properties. /// Initializes options for the authentication cookie. public CookieSigningInContext( HttpContext context, @@ -29,16 +29,10 @@ namespace Microsoft.AspNetCore.Authentication.Cookies CookieOptions cookieOptions) : base(context, scheme, options, properties) { - Principal = principal; CookieOptions = cookieOptions; + Principal = principal; } - /// - /// Contains the claims about to be converted into the outgoing cookie. - /// May be replaced or altered during the SigningIn call. - /// - public ClaimsPrincipal Principal { get; set; } - /// /// The options for creating the outgoing cookie. /// May be replace or altered during the SigningIn call. diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningOutContext.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningOutContext.cs index 0f4f4c7dcf..34f6e49ab6 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningOutContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieSigningOutContext.cs @@ -6,9 +6,9 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.Cookies { /// - /// Context object passed to the ICookieAuthenticationEvents method SigningOut + /// Context object passed to the /// - public class CookieSigningOutContext : BaseCookieContext + public class CookieSigningOutContext : PropertiesContext { /// /// @@ -25,9 +25,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies AuthenticationProperties properties, CookieOptions cookieOptions) : base(context, scheme, options, properties) - { - CookieOptions = cookieOptions; - } + => CookieOptions = cookieOptions; /// /// The options for creating the outgoing cookie. diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieValidatePrincipalContext.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieValidatePrincipalContext.cs index 3232ba52ff..d2161e42a1 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieValidatePrincipalContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieValidatePrincipalContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// Context object passed to the CookieAuthenticationEvents ValidatePrincipal method. /// - public class CookieValidatePrincipalContext : BaseCookieContext + public class CookieValidatePrincipalContext : PrincipalContext { /// /// Creates a new instance of the context object. @@ -19,33 +19,17 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// Contains the initial values for identity and extra data /// - public CookieValidatePrincipalContext(HttpContext context, AuthenticationScheme scheme, AuthenticationTicket ticket, CookieAuthenticationOptions options) + public CookieValidatePrincipalContext(HttpContext context, AuthenticationScheme scheme, CookieAuthenticationOptions options, AuthenticationTicket ticket) : base(context, scheme, options, ticket?.Properties) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - Principal = ticket.Principal; } - /// - /// Contains the claims principal arriving with the request. May be altered to change the - /// details of the authenticated user. - /// - public ClaimsPrincipal Principal { get; private set; } - /// /// If true, the cookie will be renewed /// @@ -56,18 +40,12 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// Principal property, which determines the identity of the authenticated request. /// /// The used as the replacement - public void ReplacePrincipal(ClaimsPrincipal principal) - { - Principal = principal; - } + public void ReplacePrincipal(ClaimsPrincipal principal) => Principal = principal; /// /// Called to reject the incoming principal. This may be done if the application has determined the /// account is no longer active, and the request should be treated as if it was anonymous. /// - public void RejectPrincipal() - { - Principal = null; - } + public void RejectPrincipal() => Principal = null; } } diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs new file mode 100644 index 0000000000..d12735443f --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Extensions.Logging +{ + internal static class LoggingExtensions + { + private static Action _authSchemeSignedIn; + private static Action _authSchemeSignedOut; + + static LoggingExtensions() + { + _authSchemeSignedIn = LoggerMessage.Define( + eventId: 10, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed in."); + _authSchemeSignedOut = LoggerMessage.Define( + eventId: 11, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); + } + + public static void SignedIn(this ILogger logger, string authenticationScheme) + { + _authSchemeSignedIn(logger, authenticationScheme, null); + } + + public static void SignedOut(this ILogger logger, string authenticationScheme) + { + _authSchemeSignedOut(logger, authenticationScheme, null); + } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationPostConfigureOptions.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/PostConfigureCookieAuthenticationOptions.cs similarity index 99% rename from src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationPostConfigureOptions.cs rename to src/Microsoft.AspNetCore.Authentication.Cookies/PostConfigureCookieAuthenticationOptions.cs index 38211e0f19..e6a62d1b68 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationPostConfigureOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/PostConfigureCookieAuthenticationOptions.cs @@ -55,6 +55,5 @@ namespace Microsoft.AspNetCore.Authentication.Cookies options.AccessDeniedPath = CookieAuthenticationDefaults.AccessDeniedPath; } } - } } diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json index c223c035c4..91781d2dd2 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json @@ -19,69 +19,29 @@ "TypeId": "public interface Microsoft.AspNetCore.Authentication.Cookies.ICookieAuthenticationEvents", "Kind": "Removal" }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationTicket ticket, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningOutContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties, Microsoft.AspNetCore.Http.CookieOptions cookieOptions)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningOutContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningOutContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSignedInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options, System.String authenticationScheme, System.Security.Claims.ClaimsPrincipal principal, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSignedInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options, System.String authenticationScheme, System.Security.Claims.ClaimsPrincipal principal, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties, Microsoft.AspNetCore.Http.CookieOptions cookieOptions)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieRedirectContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options, System.String redirectUri, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieRedirectContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, { "TypeId": "public static class Microsoft.AspNetCore.Builder.CookieAppBuilderExtensions", "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseCookieAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.CookieAuthenticationOptions options)", "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieRedirectContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSignedInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningInContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieSigningOutContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext : Microsoft.AspNetCore.Authentication.Cookies.BaseCookieContext", + "Kind": "Removal" } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookExtensions.cs index 4f30b9ba9c..e4dcbfee8b 100644 --- a/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookExtensions.cs @@ -2,12 +2,24 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Facebook; namespace Microsoft.Extensions.DependencyInjection { public static class FacebookAuthenticationOptionsExtensions { + public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder) + => builder.AddFacebook(FacebookDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddFacebook(FacebookDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddFacebook(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddOAuth(authenticationScheme, configureOptions); + + + // REMOVE below once callers have been updated public static IServiceCollection AddFacebookAuthentication(this IServiceCollection services) => services.AddFacebookAuthentication(FacebookDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookHandler.cs b/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookHandler.cs index c94048f9b9..8a1f29bbe5 100644 --- a/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Facebook/FacebookHandler.cs @@ -42,13 +42,13 @@ namespace Microsoft.AspNetCore.Authentication.Facebook var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Scheme.Name); - var context = new OAuthCreatingTicketContext(ticket, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); context.RunClaimActions(); await Events.CreatingTicket(context); - return context.Ticket; + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } private string GenerateAppSecretProof(string accessToken) diff --git a/src/Microsoft.AspNetCore.Authentication.Google/GoogleExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Google/GoogleExtensions.cs index be40822c57..ee42c5564c 100644 --- a/src/Microsoft.AspNetCore.Authentication.Google/GoogleExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Google/GoogleExtensions.cs @@ -2,12 +2,25 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Google; namespace Microsoft.Extensions.DependencyInjection { public static class GoogleExtensions { + public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder) + => builder.AddGoogle(GoogleDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddGoogle(GoogleDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddGoogle(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddOAuth(authenticationScheme, configureOptions); + + + // REMOVE below once callers have been updated + public static IServiceCollection AddGoogleAuthentication(this IServiceCollection services) => services.AddGoogleAuthentication(GoogleDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.Google/GoogleHandler.cs b/src/Microsoft.AspNetCore.Authentication.Google/GoogleHandler.cs index 3a93c5cbf7..aa5e596494 100644 --- a/src/Microsoft.AspNetCore.Authentication.Google/GoogleHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Google/GoogleHandler.cs @@ -39,14 +39,11 @@ namespace Microsoft.AspNetCore.Authentication.Google var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - var principal = new ClaimsPrincipal(identity); - var ticket = new AuthenticationTicket(principal, properties, Scheme.Name); - var context = new OAuthCreatingTicketContext(ticket, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); context.RunClaimActions(); await Events.CreatingTicket(context); - - return context.Ticket; + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } // TODO: Abstract this properties override pattern into the base class? diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/AuthenticationFailedContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/AuthenticationFailedContext.cs index b47a9bab0f..1c2efd6c73 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/AuthenticationFailedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/AuthenticationFailedContext.cs @@ -6,12 +6,13 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class AuthenticationFailedContext : BaseJwtBearerContext + public class AuthenticationFailedContext : ResultContext { - public AuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, JwtBearerOptions options) - : base(context, scheme, options) - { - } + public AuthenticationFailedContext( + HttpContext context, + AuthenticationScheme scheme, + JwtBearerOptions options) + : base(context, scheme, options) { } public Exception Exception { get; set; } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/BaseJwtBearerContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/BaseJwtBearerContext.cs deleted file mode 100644 index 313e999d0d..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/BaseJwtBearerContext.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.JwtBearer -{ - public class BaseJwtBearerContext : BaseControlContext - { - public BaseJwtBearerContext(HttpContext context, AuthenticationScheme scheme, JwtBearerOptions options) - : base(context) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (scheme == null) - { - throw new ArgumentNullException(nameof(scheme)); - } - - Options = options; - Scheme = scheme; - } - - public JwtBearerOptions Options { get; } - - public AuthenticationScheme Scheme { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerChallengeContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerChallengeContext.cs index e6f931f6db..6500e1e3f7 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerChallengeContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerChallengeContext.cs @@ -6,15 +6,14 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class JwtBearerChallengeContext : BaseJwtBearerContext + public class JwtBearerChallengeContext : PropertiesContext { - public JwtBearerChallengeContext(HttpContext context, AuthenticationScheme scheme, JwtBearerOptions options, AuthenticationProperties properties) - : base(context, scheme, options) - { - Properties = properties; - } - - public AuthenticationProperties Properties { get; } + public JwtBearerChallengeContext( + HttpContext context, + AuthenticationScheme scheme, + JwtBearerOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) { } /// /// Any failures encountered during the authentication process. @@ -40,5 +39,15 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer /// WWW-Authenticate header. This property is always null unless explicitly set. /// public string ErrorUri { get; set; } + + /// + /// If true, will skip any default logic for this challenge. + /// + public bool Handled { get; private set; } + + /// + /// Skips any default logic for this challenge. + /// + public void HandleResponse() => Handled = true; } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs index 530a945cab..3c263f6b24 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs @@ -5,12 +5,13 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class MessageReceivedContext : BaseJwtBearerContext + public class MessageReceivedContext : ResultContext { - public MessageReceivedContext(HttpContext context, AuthenticationScheme scheme, JwtBearerOptions options) - : base(context, scheme, options) - { - } + public MessageReceivedContext( + HttpContext context, + AuthenticationScheme scheme, + JwtBearerOptions options) + : base(context, scheme, options) { } /// /// Bearer Token. This will give application an opportunity to retrieve token from an alternation location. diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs index 3667865da1..39b677b96d 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs @@ -6,12 +6,13 @@ using Microsoft.IdentityModel.Tokens; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class TokenValidatedContext : BaseJwtBearerContext + public class TokenValidatedContext : ResultContext { - public TokenValidatedContext(HttpContext context, AuthenticationScheme scheme, JwtBearerOptions options) - : base(context, scheme, options) - { - } + public TokenValidatedContext( + HttpContext context, + AuthenticationScheme scheme, + JwtBearerOptions options) + : base(context, scheme, options) { } public SecurityToken SecurityToken { get; set; } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerExtensions.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerExtensions.cs index 582eb6314d..4f051bd39a 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -10,6 +11,20 @@ namespace Microsoft.Extensions.DependencyInjection { public static class JwtBearerExtensions { + public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder) + => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, JwtBearerPostConfigureOptions>()); + return builder.AddScheme(authenticationScheme, configureOptions); + } + + + // REMOVE once callers updated public static IServiceCollection AddJwtBearerAuthentication(this IServiceCollection services) => services.AddJwtBearerAuthentication(JwtBearerDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index 0087d38d0f..999d323e11 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; @@ -13,7 +12,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; @@ -47,7 +45,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer protected override async Task HandleAuthenticateAsync() { string token = null; - AuthenticateResult result = null; try { // Give application opportunity to find from a different location, adjust, or reject token @@ -55,9 +52,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer // event can set the token await Events.MessageReceived(messageReceivedContext); - if (messageReceivedContext.IsProcessingComplete(out result)) + if (messageReceivedContext.Result != null) { - return result; + return messageReceivedContext.Result; } // If application retrieved token from somewhere else, use that. @@ -70,7 +67,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer // If no authorization header found, nothing to process further if (string.IsNullOrEmpty(authorization)) { - return AuthenticateResult.None(); + return AuthenticateResult.NoResult(); } if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) @@ -81,7 +78,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer // If no token found, no further work possible if (string.IsNullOrEmpty(token)) { - return AuthenticateResult.None(); + return AuthenticateResult.NoResult(); } } @@ -138,29 +135,28 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Logger.TokenValidationSucceeded(); - var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name); var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options) { - Ticket = ticket, - SecurityToken = validatedToken, + Principal = principal, + SecurityToken = validatedToken }; await Events.TokenValidated(tokenValidatedContext); - if (tokenValidatedContext.IsProcessingComplete(out result)) + if (tokenValidatedContext.Result != null) { - return result; + return tokenValidatedContext.Result; } - ticket = tokenValidatedContext.Ticket; if (Options.SaveToken) { - ticket.Properties.StoreTokens(new[] + tokenValidatedContext.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } - return AuthenticateResult.Success(ticket); + tokenValidatedContext.Success(); + return tokenValidatedContext.Result; } } @@ -172,9 +168,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }; await Events.AuthenticationFailed(authenticationFailedContext); - if (authenticationFailedContext.IsProcessingComplete(out result)) + if (authenticationFailedContext.Result != null) { - return result; + return authenticationFailedContext.Result; } return AuthenticateResult.Fail(authenticationFailedContext.Exception); @@ -192,9 +188,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }; await Events.AuthenticationFailed(authenticationFailedContext); - if (authenticationFailedContext.IsProcessingComplete(out result)) + if (authenticationFailedContext.Result != null) { - return result; + return authenticationFailedContext.Result; } throw; @@ -217,7 +213,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } await Events.Challenge(eventContext); - if (eventContext.IsProcessingComplete(out var ignored)) + if (eventContext.Handled) { return; } @@ -329,15 +325,5 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer return string.Join("; ", messages); } - - protected override Task HandleSignOutAsync(AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - - protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - throw new NotSupportedException(); - } } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json index d810a2bb90..223ff401b4 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json @@ -15,44 +15,29 @@ "TypeId": "public interface Microsoft.AspNetCore.Authentication.JwtBearer.IJwtBearerEvents", "Kind": "Removal" }, + { + "TypeId": "public static class Microsoft.AspNetCore.Builder.JwtBearerAppBuilderExtensions", + "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseJwtBearerAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", + "Kind": "Removal" + }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.AuthenticationFailedContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", "Kind": "Removal" }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", "Kind": "Removal" }, { - "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public Microsoft.AspNetCore.Builder.JwtBearerOptions get_Options()", + "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerChallengeContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", "Kind": "Removal" }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.MessageReceivedContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", "Kind": "Removal" }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerChallengeContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.JwtBearerOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerChallengeContext : Microsoft.AspNetCore.Authentication.JwtBearer.BaseJwtBearerContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public static class Microsoft.AspNetCore.Builder.JwtBearerAppBuilderExtensions", - "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseJwtBearerAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.JwtBearerOptions options)", "Kind": "Removal" } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountExtensions.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountExtensions.cs index a4ad5692cd..9a53fd7700 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountExtensions.cs @@ -2,12 +2,25 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; namespace Microsoft.Extensions.DependencyInjection { public static class MicrosoftAccountExtensions { + public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder) + => builder.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddOAuth(authenticationScheme, configureOptions); + + + // REMOVE below once callers have been updated + public static IServiceCollection AddMicrosoftAccountAuthentication(this IServiceCollection services) => services.AddMicrosoftAccountAuthentication(MicrosoftAccountDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs index 815b94bf40..45fae3d0ea 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs @@ -32,12 +32,11 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Scheme.Name); - var context = new OAuthCreatingTicketContext(ticket, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); context.RunClaimActions(); await Events.CreatingTicket(context); - return context.Ticket; + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } } } diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthCreatingTicketContext.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthCreatingTicketContext.cs index 6e31056392..f660dd2247 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthCreatingTicketContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthCreatingTicketContext.cs @@ -13,32 +13,34 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// /// Contains information about the login session as well as the user . /// - public class OAuthCreatingTicketContext : BaseAuthenticationContext + public class OAuthCreatingTicketContext : ResultContext { /// /// Initializes a new . /// - /// The . + /// The . + /// The . /// The HTTP environment. /// The authentication scheme. /// The options used by the authentication middleware. /// The HTTP client used by the authentication middleware /// The tokens returned from the token endpoint. public OAuthCreatingTicketContext( - AuthenticationTicket ticket, + ClaimsPrincipal principal, + AuthenticationProperties properties, HttpContext context, AuthenticationScheme scheme, OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens) - : this(ticket, context, scheme, options, backchannel, tokens, user: new JObject()) - { - } + : this(principal, properties, context, scheme, options, backchannel, tokens, user: new JObject()) + { } /// /// Initializes a new . /// - /// The . + /// The . + /// The . /// The HTTP environment. /// The authentication scheme. /// The options used by the authentication middleware. @@ -46,25 +48,16 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// The tokens returned from the token endpoint. /// The JSON-serialized user. public OAuthCreatingTicketContext( - AuthenticationTicket ticket, + ClaimsPrincipal principal, + AuthenticationProperties properties, HttpContext context, AuthenticationScheme scheme, OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens, JObject user) - : base(context, scheme.Name, ticket.Properties) + : base(context, scheme, options) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - if (backchannel == null) { throw new ArgumentNullException(nameof(backchannel)); @@ -80,23 +73,13 @@ namespace Microsoft.AspNetCore.Authentication.OAuth throw new ArgumentNullException(nameof(user)); } - if (scheme == null) - { - throw new ArgumentNullException(nameof(scheme)); - } - TokenResponse = tokens; Backchannel = backchannel; User = user; - Options = options; - Scheme = scheme; - Ticket = ticket; + Principal = principal; + Properties = properties; } - public OAuthOptions Options { get; } - - public AuthenticationScheme Scheme { get; } - /// /// Gets the JSON-serialized user or an empty /// if it is not available. @@ -146,20 +129,12 @@ namespace Microsoft.AspNetCore.Authentication.OAuth public HttpClient Backchannel { get; } /// - /// The that will be created. + /// Gets the main identity exposed by the authentication ticket. + /// This property returns null when the ticket is null. /// - public AuthenticationTicket Ticket { get; set; } + public ClaimsIdentity Identity => Principal?.Identity as ClaimsIdentity; - /// - /// Gets the main identity exposed by . - /// This property returns null when is null. - /// - public ClaimsIdentity Identity => Ticket?.Principal.Identity as ClaimsIdentity; - - public void RunClaimActions() - { - RunClaimActions(User); - } + public void RunClaimActions() => RunClaimActions(User); public void RunClaimActions(JObject userData) { diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthEvents.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthEvents.cs index b3572cab4c..9e194491b9 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthEvents.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// /// Gets or sets the delegate that is invoked when the RedirectToAuthorizationEndpoint method is invoked. /// - public Func OnRedirectToAuthorizationEndpoint { get; set; } = context => + public Func, Task> OnRedirectToAuthorizationEndpoint { get; set; } = context => { context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// /// Called when a Challenge causes a redirect to authorize endpoint in the OAuth handler. /// - /// Contains redirect URI and of the challenge. - public virtual Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) => OnRedirectToAuthorizationEndpoint(context); + /// Contains redirect URI and of the challenge. + public virtual Task RedirectToAuthorizationEndpoint(RedirectContext context) => OnRedirectToAuthorizationEndpoint(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthRedirectToAuthorizationContext.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthRedirectToAuthorizationContext.cs deleted file mode 100644 index 5d5e0e701a..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/Events/OAuthRedirectToAuthorizationContext.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.OAuth -{ - /// - /// Context passed when a Challenge causes a redirect to authorize endpoint in the handler. - /// - public class OAuthRedirectToAuthorizationContext : BaseContext - { - /// - /// Creates a new context object. - /// - /// The HTTP request context. - /// The . - /// The authentication properties of the challenge. - /// The initial redirect URI. - public OAuthRedirectToAuthorizationContext(HttpContext context, OAuthOptions options, AuthenticationProperties properties, string redirectUri) - : base(context) - { - RedirectUri = redirectUri; - Properties = properties; - Options = options; - } - - public OAuthOptions Options { get; } - - /// - /// Gets the URI used for the redirect operation. - /// - public string RedirectUri { get; private set; } - - /// - /// Gets the authentication properties of the challenge. - /// - public AuthenticationProperties Properties { get; private set; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthExtensions.cs index 4ad0a83362..5720d8e4f4 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -10,6 +11,18 @@ namespace Microsoft.Extensions.DependencyInjection { public static class OAuthExtensions { + public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddOAuth>(authenticationScheme, configureOptions); + + public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + where TOptions : OAuthOptions, new() + where THandler : OAuthHandler + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OAuthPostConfigureOptions>()); + return builder.AddRemoteScheme(authenticationScheme, authenticationScheme, configureOptions); + } + + // REMOVE below once callers have been updated public static IServiceCollection AddOAuthAuthentication(this IServiceCollection services, string authenticationScheme, Action configureOptions) { return services.AddOAuthAuthentication>(authenticationScheme, configureOptions); diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs index bbe4f4038b..b61d575375 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// A new instance of the events instance. protected override Task CreateEventsAsync() => Task.FromResult(new OAuthEvents()); - protected override async Task HandleRemoteAuthenticateAsync() + protected override async Task HandleRemoteAuthenticateAsync() { AuthenticationProperties properties = null; var query = Request.Query; @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth failureMessage.Append(";Uri=").Append(errorUri); } - return AuthenticateResult.Fail(failureMessage.ToString()); + return HandleRequestResult.Fail(failureMessage.ToString()); } var code = query["code"]; @@ -72,30 +72,30 @@ namespace Microsoft.AspNetCore.Authentication.OAuth properties = Options.StateDataFormat.Unprotect(state); if (properties == null) { - return AuthenticateResult.Fail("The oauth state was missing or invalid."); + return HandleRequestResult.Fail("The oauth state was missing or invalid."); } // OAuth2 10.12 CSRF if (!ValidateCorrelationId(properties)) { - return AuthenticateResult.Fail("Correlation failed."); + return HandleRequestResult.Fail("Correlation failed."); } if (StringValues.IsNullOrEmpty(code)) { - return AuthenticateResult.Fail("Code was not found."); + return HandleRequestResult.Fail("Code was not found."); } var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath)); if (tokens.Error != null) { - return AuthenticateResult.Fail(tokens.Error); + return HandleRequestResult.Fail(tokens.Error); } if (string.IsNullOrEmpty(tokens.AccessToken)) { - return AuthenticateResult.Fail("Failed to retrieve access token."); + return HandleRequestResult.Fail("Failed to retrieve access token."); } var identity = new ClaimsIdentity(ClaimsIssuer); @@ -137,11 +137,11 @@ namespace Microsoft.AspNetCore.Authentication.OAuth var ticket = await CreateTicketAsync(identity, properties, tokens); if (ticket != null) { - return AuthenticateResult.Success(ticket); + return HandleRequestResult.Success(ticket); } else { - return AuthenticateResult.Fail("Failed to retrieve user information from remote server."); + return HandleRequestResult.Fail("Failed to retrieve user information from remote server."); } } @@ -185,10 +185,9 @@ namespace Microsoft.AspNetCore.Authentication.OAuth protected virtual async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { - var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Scheme.Name); - var context = new OAuthCreatingTicketContext(ticket, Context, Scheme, Options, Backchannel, tokens); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens); await Events.CreatingTicket(context); - return context.Ticket; + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) @@ -202,8 +201,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth GenerateCorrelationId(properties); var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath)); - var redirectContext = new OAuthRedirectToAuthorizationContext( - Context, Options, + var redirectContext = new RedirectContext( + Context, Scheme, Options, properties, authorizationEndpoint); await Events.RedirectToAuthorizationEndpoint(redirectContext); } diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication.OAuth/breakingchanges.netcore.json index 96fd03e985..3124d7fe70 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/breakingchanges.netcore.json @@ -23,24 +23,13 @@ "TypeId": "public interface Microsoft.AspNetCore.Authentication.OAuth.IOAuthEvents : Microsoft.AspNetCore.Authentication.IRemoteAuthenticationEvents", "Kind": "Removal" }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OAuth.OAuthRedirectToAuthorizationContext : Microsoft.AspNetCore.Authentication.BaseContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OAuthOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties, System.String redirectUri)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OAuth.OAuthRedirectToAuthorizationContext : Microsoft.AspNetCore.Authentication.BaseContext", - "MemberId": "public Microsoft.AspNetCore.Builder.OAuthOptions get_Options()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OAuth.OAuthRedirectToAuthorizationContext : Microsoft.AspNetCore.Authentication.BaseContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, { "TypeId": "public static class Microsoft.AspNetCore.Builder.OAuthAppBuilderExtensions", "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseOAuthAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.OAuthOptions options)", "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OAuth.OAuthRedirectToAuthorizationContext : Microsoft.AspNetCore.Authentication.BaseContext", + "Kind": "Removal" } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationFailedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationFailedContext.cs index 0c7d968638..203da93c53 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationFailedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationFailedContext.cs @@ -2,17 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class AuthenticationFailedContext : BaseOpenIdConnectContext + public class AuthenticationFailedContext : RemoteAuthenticationContext { public AuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context, scheme, options) - { - } + : base(context, scheme, options, new AuthenticationProperties()) + { } + + public OpenIdConnectMessage ProtocolMessage { get; set; } public Exception Exception { get; set; } } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs index 0ccfc3ab71..bdf6e4a7ff 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationCodeReceivedContext.cs @@ -3,9 +3,7 @@ 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 @@ -13,17 +11,19 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// This Context can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol. /// - public class AuthorizationCodeReceivedContext : BaseOpenIdConnectContext + public class AuthorizationCodeReceivedContext : RemoteAuthenticationContext { /// /// Creates a /// - public AuthorizationCodeReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context, scheme, options) - { - } + public AuthorizationCodeReceivedContext( + HttpContext context, + AuthenticationScheme scheme, + OpenIdConnectOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) { } - public AuthenticationProperties Properties { get; set; } + public OpenIdConnectMessage ProtocolMessage { get; set; } /// /// Gets or sets the that was received in the authentication response, if any. diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/BaseOpenIdConnectContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/BaseOpenIdConnectContext.cs deleted file mode 100644 index 63f815d9ee..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/BaseOpenIdConnectContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; - -namespace Microsoft.AspNetCore.Authentication.OpenIdConnect -{ - public class BaseOpenIdConnectContext : BaseControlContext - { - public BaseOpenIdConnectContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context) - { - Options = options ?? throw new ArgumentNullException(nameof(options)); - Scheme = scheme ?? throw new ArgumentNullException(nameof(scheme)); - } - - public OpenIdConnectOptions Options { get; } - - public AuthenticationScheme Scheme { get; } - - public OpenIdConnectMessage ProtocolMessage { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs index f0298ed055..106ecb8c03 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs @@ -2,21 +2,24 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class MessageReceivedContext : BaseOpenIdConnectContext + public class MessageReceivedContext : RemoteAuthenticationContext { - public MessageReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context, scheme, options) - { - } + public MessageReceivedContext( + HttpContext context, + AuthenticationScheme scheme, + OpenIdConnectOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) { } + + public OpenIdConnectMessage ProtocolMessage { get; set; } /// /// Bearer Token. This will give application an opportunity to retrieve token from an alternation location. /// public string Token { get; set; } - - public AuthenticationProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RedirectContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RedirectContext.cs index 59b00827a3..9961c237d4 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RedirectContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RedirectContext.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { @@ -9,14 +10,25 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// When a user configures the to be notified prior to redirecting to an IdentityProvider /// an instance of is passed to the 'RedirectToAuthenticationEndpoint' or 'RedirectToEndSessionEndpoint' events. /// - public class RedirectContext : BaseOpenIdConnectContext + public class RedirectContext : PropertiesContext { - public RedirectContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, AuthenticationProperties properties) - : base(context, scheme, options) - { - Properties = properties; - } + public RedirectContext( + HttpContext context, + AuthenticationScheme scheme, + OpenIdConnectOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) { } - public AuthenticationProperties Properties { get; } + public OpenIdConnectMessage ProtocolMessage { get; set; } + + /// + /// If true, will skip any default logic for this redirect. + /// + public bool Handled { get; private set; } + + /// + /// Skips any default logic for this redirect. + /// + public void HandleResponse() => Handled = true; } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RemoteSignoutContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RemoteSignoutContext.cs index 5c0172673c..26720a58f8 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RemoteSignoutContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/RemoteSignoutContext.cs @@ -6,16 +6,12 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class RemoteSignOutContext : BaseOpenIdConnectContext + public class RemoteSignOutContext : RemoteAuthenticationContext { - public RemoteSignOutContext( - HttpContext context, - AuthenticationScheme scheme, - OpenIdConnectOptions options, - OpenIdConnectMessage message) - : base(context, scheme, options) - { - ProtocolMessage = message; - } + public RemoteSignOutContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, OpenIdConnectMessage message) + : base(context, scheme, options, new AuthenticationProperties()) + => ProtocolMessage = message; + + public OpenIdConnectMessage ProtocolMessage { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs index 7c0d51fbbd..2bebdb8dc5 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs @@ -1,6 +1,7 @@ // 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.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Protocols.OpenIdConnect; @@ -9,18 +10,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint. /// - public class TokenResponseReceivedContext : BaseOpenIdConnectContext + public class TokenResponseReceivedContext : RemoteAuthenticationContext { /// /// Creates a /// - public TokenResponseReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, AuthenticationProperties properties) - : base(context, scheme, options) - { - Properties = properties; - } + public TokenResponseReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, ClaimsPrincipal user, AuthenticationProperties properties) + : base(context, scheme, options, properties) + => Principal = user; - public AuthenticationProperties Properties { get; } + public OpenIdConnectMessage ProtocolMessage { get; set; } /// /// Gets or sets the that contains the tokens received after redeeming the code at the token endpoint. diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs index fea89298ce..853857dc7b 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs @@ -2,26 +2,23 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IdentityModel.Tokens.Jwt; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; +using System.Security.Claims; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class TokenValidatedContext : BaseOpenIdConnectContext + public class TokenValidatedContext : RemoteAuthenticationContext { /// /// Creates a /// - public TokenValidatedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context, scheme, options) - { - } + public TokenValidatedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, ClaimsPrincipal principal, AuthenticationProperties properties) + : base(context, scheme, options, properties) + => Principal = principal; + + public OpenIdConnectMessage ProtocolMessage { get; set; } - public AuthenticationProperties Properties { get; set; } - public JwtSecurityToken SecurityToken { get; set; } public OpenIdConnectMessage TokenEndpointResponse { get; set; } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs index ee80cb71fe..0b855eaf39 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs @@ -1,17 +1,20 @@ // 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.Security.Claims; using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class UserInformationReceivedContext : BaseOpenIdConnectContext + public class UserInformationReceivedContext : RemoteAuthenticationContext { - public UserInformationReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options) - : base(context, scheme, options) - { - } + public UserInformationReceivedContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, ClaimsPrincipal principal, AuthenticationProperties properties) + : base(context, scheme, options, properties) + => Principal = principal; + + public OpenIdConnectMessage ProtocolMessage { get; set; } public JObject User { get; set; } } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs index aa6d0cbaa7..458cfd73ee 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs @@ -8,9 +8,7 @@ namespace Microsoft.Extensions.Logging internal static class LoggingExtensions { private static Action _redirectToIdentityProviderForSignOutHandledResponse; - private static Action _redirectToIdentityProviderForSignOutSkipped; private static Action _redirectToIdentityProviderHandledResponse; - private static Action _redirectToIdentityProviderSkipped; private static Action _updatingConfiguration; private static Action _receivedIdToken; private static Action _redeemingCodeForTokens; @@ -55,6 +53,7 @@ namespace Microsoft.Extensions.Logging private static Action _remoteSignOut; private static Action _remoteSignOutSessionIdMissing; private static Action _remoteSignOutSessionIdInvalid; + private static Action _signOut; static LoggingExtensions() { @@ -63,10 +62,6 @@ namespace Microsoft.Extensions.Logging eventId: 1, logLevel: LogLevel.Debug, formatString: "RedirectToIdentityProviderForSignOut.HandledResponse"); - _redirectToIdentityProviderForSignOutSkipped = LoggerMessage.Define( - eventId: 2, - logLevel: LogLevel.Debug, - formatString: "RedirectToIdentityProviderForSignOut.Skipped"); _invalidLogoutQueryStringRedirectUrl = LoggerMessage.Define( eventId: 3, logLevel: LogLevel.Warning, @@ -87,10 +82,6 @@ namespace Microsoft.Extensions.Logging eventId: 6, logLevel: LogLevel.Debug, formatString: "RedirectToIdentityProvider.HandledResponse"); - _redirectToIdentityProviderSkipped = LoggerMessage.Define( - eventId: 7, - logLevel: LogLevel.Debug, - formatString: "RedirectToIdentityProvider.Skipped"); _invalidAuthenticationRequestUrl = LoggerMessage.Define( eventId: 8, logLevel: LogLevel.Warning, @@ -253,6 +244,10 @@ namespace Microsoft.Extensions.Logging logLevel: LogLevel.Error, formatString: "The remote signout request was ignored because the 'sid' parameter didn't match " + "the expected value, which may indicate an unsolicited logout."); + _signOut = LoggerMessage.Define( + eventId: 49, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); } public static void UpdatingConfiguration(this ILogger logger) @@ -345,21 +340,11 @@ namespace Microsoft.Extensions.Logging _redirectToIdentityProviderForSignOutHandledResponse(logger, null); } - public static void RedirectToIdentityProviderForSignOutSkipped(this ILogger logger) - { - _redirectToIdentityProviderForSignOutSkipped(logger, null); - } - public static void RedirectToIdentityProviderHandledResponse(this ILogger logger) { _redirectToIdentityProviderHandledResponse(logger, null); } - public static void RedirectToIdentityProviderSkipped(this ILogger logger) - { - _redirectToIdentityProviderSkipped(logger, null); - } - public static void UserInformationReceivedHandledResponse(this ILogger logger) { _userInformationReceivedHandledResponse(logger, null); @@ -494,5 +479,10 @@ namespace Microsoft.Extensions.Logging { _remoteSignOutSessionIdInvalid(logger, null); } + + public static void SignedOut(this ILogger logger, string authenticationScheme) + { + _signOut(logger, authenticationScheme, null); + } } } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectExtensions.cs index d576409047..7ba262bf39 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -10,6 +11,19 @@ namespace Microsoft.Extensions.DependencyInjection { public static class OpenIdConnectExtensions { + public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder) + => builder.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OpenIdConnectPostConfigureOptions>()); + return builder.AddRemoteScheme(authenticationScheme, authenticationScheme, configureOptions); + } + + // REMOVE once callers have been updated public static IServiceCollection AddOpenIdConnectAuthentication(this IServiceCollection services) => services.AddOpenIdConnectAuthentication(OpenIdConnectDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 1dec541970..341abbf5a2 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware. /// - public class OpenIdConnectHandler : RemoteAuthenticationHandler + public class OpenIdConnectHandler : RemoteAuthenticationHandler, IAuthenticationSignOutHandler { private const string NonceProperty = "N"; private const string UriSchemeDelimiter = "://"; @@ -110,15 +110,18 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect var remoteSignOutContext = new RemoteSignOutContext(Context, Scheme, Options, message); await Events.RemoteSignOut(remoteSignOutContext); - if (remoteSignOutContext.HandledResponse) + if (remoteSignOutContext.Result != null) { - Logger.RemoteSignOutHandledResponse(); - return true; - } - if (remoteSignOutContext.Skipped) - { - Logger.RemoteSignOutSkipped(); - return false; + if (remoteSignOutContext.Result.Handled) + { + Logger.RemoteSignOutHandledResponse(); + return true; + } + if (remoteSignOutContext.Result.Skipped) + { + Logger.RemoteSignOutSkipped(); + return false; + } } if (message == null) @@ -161,8 +164,10 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// Redirect user to the identity provider for sign out /// /// A task executing the sign out procedure - protected override async Task HandleSignOutAsync(AuthenticationProperties properties) + public async virtual Task SignOutAsync(AuthenticationProperties properties) { + properties = properties ?? new AuthenticationProperties(); + Logger.EnteringOpenIdAuthenticationHandlerHandleSignOutAsync(GetType().FullName); if (_configuration == null && Options.ConfigurationManager != null) @@ -199,16 +204,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect }; await Events.RedirectToIdentityProviderForSignOut(redirectContext); - if (redirectContext.HandledResponse) + if (redirectContext.Handled) { Logger.RedirectToIdentityProviderForSignOutHandledResponse(); return; } - else if (redirectContext.Skipped) - { - Logger.RedirectToIdentityProviderForSignOutSkipped(); - return; - } message = redirectContext.ProtocolMessage; @@ -221,8 +221,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (string.IsNullOrEmpty(message.IssuerAddress)) { - throw new InvalidOperationException( - "Cannot redirect to the end session endpoint, the configuration may be missing or invalid."); + throw new InvalidOperationException("Cannot redirect to the end session endpoint, the configuration may be missing or invalid."); } if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) @@ -266,6 +265,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { throw new NotImplementedException($"An unsupported authentication method has been configured: {Options.AuthenticationMethod}"); } + + Logger.SignedOut(Scheme.Name); } /// @@ -343,16 +344,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect }; await Events.RedirectToIdentityProvider(redirectContext); - if (redirectContext.HandledResponse) + if (redirectContext.Handled) { Logger.RedirectToIdentityProviderHandledResponse(); return; } - else if (redirectContext.Skipped) - { - Logger.RedirectToIdentityProviderSkipped(); - return; - } message = redirectContext.ProtocolMessage; @@ -418,8 +414,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// Invoked to process incoming OpenIdConnect messages. /// - /// An if successful. - protected override async Task HandleRemoteAuthenticateAsync() + /// An . + protected override async Task HandleRemoteAuthenticateAsync() { Logger.EnteringOpenIdAuthenticationHandlerHandleRemoteAuthenticateAsync(GetType().FullName); @@ -437,9 +433,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (Options.SkipUnrecognizedRequests) { // Not for us? - return AuthenticateResult.None(); + return HandleRequestResult.SkipHandler(); } - return AuthenticateResult.Fail("An OpenID Connect response cannot contain an " + + return HandleRequestResult.Fail("An OpenID Connect response cannot contain an " + "identity token or an access token when using response_mode=query"); } } @@ -459,13 +455,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (Options.SkipUnrecognizedRequests) { // Not for us? - return AuthenticateResult.None(); + return HandleRequestResult.SkipHandler(); } - return AuthenticateResult.Fail("No message."); + return HandleRequestResult.Fail("No message."); } - AuthenticateResult result; - try { AuthenticationProperties properties = null; @@ -475,9 +469,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse, properties); - if (messageReceivedContext.IsProcessingComplete(out result)) + if (messageReceivedContext.Result != null) { - return result; + return messageReceivedContext.Result; } authorizationResponse = messageReceivedContext.ProtocolMessage; properties = messageReceivedContext.Properties; @@ -491,9 +485,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect Logger.NullOrEmptyAuthorizationResponseState(); if (Options.SkipUnrecognizedRequests) { - return AuthenticateResult.None(); + return HandleRequestResult.SkipHandler(); } - return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty); + return HandleRequestResult.Fail(Resources.MessageStateIsNullOrEmpty); } // if state exists and we failed to 'unprotect' this is not a message we should process. @@ -506,9 +500,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (Options.SkipUnrecognizedRequests) { // Not for us? - return AuthenticateResult.None(); + return HandleRequestResult.SkipHandler(); } - return AuthenticateResult.Fail(Resources.MessageStateIsInvalid); + return HandleRequestResult.Fail(Resources.MessageStateIsInvalid); } string userstate = null; @@ -517,13 +511,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (!ValidateCorrelationId(properties)) { - return AuthenticateResult.Fail("Correlation failed."); + return HandleRequestResult.Fail("Correlation failed."); } // if any of the error fields are set, throw error null if (!string.IsNullOrEmpty(authorizationResponse.Error)) { - return AuthenticateResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null)); + return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null)); } if (_configuration == null && Options.ConfigurationManager != null) @@ -534,7 +528,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect PopulateSessionProperties(authorizationResponse, properties); - AuthenticationTicket ticket = null; + ClaimsPrincipal user = null; JwtSecurityToken jwt = null; string nonce = null; var validationParameters = Options.TokenValidationParameters.Clone(); @@ -543,7 +537,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (!string.IsNullOrEmpty(authorizationResponse.IdToken)) { Logger.ReceivedIdToken(); - ticket = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt); + user = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt); nonce = jwt.Payload.Nonce; if (!string.IsNullOrEmpty(nonce)) @@ -551,14 +545,14 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect nonce = ReadNonceCookie(nonce); } - var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, null, properties, ticket, jwt, nonce); - if (tokenValidatedContext.IsProcessingComplete(out result)) + var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, null, user, properties, jwt, nonce); + if (tokenValidatedContext.Result != null) { - return result; + return tokenValidatedContext.Result; } authorizationResponse = tokenValidatedContext.ProtocolMessage; + user = tokenValidatedContext.Principal; properties = tokenValidatedContext.Properties; - ticket = tokenValidatedContext.Ticket; jwt = tokenValidatedContext.SecurityToken; nonce = tokenValidatedContext.Nonce; } @@ -576,17 +570,17 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect // Authorization Code or Hybrid flow if (!string.IsNullOrEmpty(authorizationResponse.Code)) { - var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, properties, ticket, jwt); - if (authorizationCodeReceivedContext.IsProcessingComplete(out result)) + var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, user, properties, jwt); + if (authorizationCodeReceivedContext.Result != null) { - return result; + return authorizationCodeReceivedContext.Result; } authorizationResponse = authorizationCodeReceivedContext.ProtocolMessage; + user = authorizationCodeReceivedContext.Principal; properties = authorizationCodeReceivedContext.Properties; var tokenEndpointRequest = authorizationCodeReceivedContext.TokenEndpointRequest; // If the developer redeemed the code themselves... tokenEndpointResponse = authorizationCodeReceivedContext.TokenEndpointResponse; - ticket = authorizationCodeReceivedContext.Ticket; jwt = authorizationCodeReceivedContext.JwtSecurityToken; if (!authorizationCodeReceivedContext.HandledCodeRedemption) @@ -594,14 +588,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest); } - var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties, ticket); - if (tokenResponseReceivedContext.IsProcessingComplete(out result)) + var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, user, properties); + if (tokenResponseReceivedContext.Result != null) { - return result; + return tokenResponseReceivedContext.Result; } authorizationResponse = tokenResponseReceivedContext.ProtocolMessage; tokenEndpointResponse = tokenResponseReceivedContext.TokenEndpointResponse; + user = tokenResponseReceivedContext.Principal; + properties = tokenResponseReceivedContext.Properties; // no need to validate signature when token is received using "code flow" as per spec // [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation]. @@ -610,10 +606,10 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect // At least a cursory validation is required on the new IdToken, even if we've already validated the one from the authorization response. // And we'll want to validate the new JWT in ValidateTokenResponse. JwtSecurityToken tokenEndpointJwt; - var tokenEndpointTicket = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out tokenEndpointJwt); + var tokenEndpointUser = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out tokenEndpointJwt); // Avoid reading & deleting the nonce cookie, running the event, etc, if it was already done as part of the authorization response validation. - if (ticket == null) + if (user == null) { nonce = tokenEndpointJwt.Payload.Nonce; if (!string.IsNullOrEmpty(nonce)) @@ -621,15 +617,15 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect nonce = ReadNonceCookie(nonce); } - var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, tokenEndpointResponse, properties, tokenEndpointTicket, tokenEndpointJwt, nonce); - if (tokenValidatedContext.IsProcessingComplete(out result)) + var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, tokenEndpointResponse, tokenEndpointUser, properties, tokenEndpointJwt, nonce); + if (tokenValidatedContext.Result != null) { - return result; + return tokenValidatedContext.Result; } authorizationResponse = tokenValidatedContext.ProtocolMessage; tokenEndpointResponse = tokenValidatedContext.TokenEndpointResponse; + user = tokenValidatedContext.Principal; properties = tokenValidatedContext.Properties; - ticket = tokenValidatedContext.Ticket; jwt = tokenValidatedContext.SecurityToken; nonce = tokenValidatedContext.Nonce; } @@ -658,23 +654,23 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (Options.SaveTokens) { - SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse); + SaveTokens(properties, tokenEndpointResponse ?? authorizationResponse); } if (Options.GetClaimsFromUserInfoEndpoint) { - return await GetUserInformationAsync(tokenEndpointResponse ?? authorizationResponse, jwt, ticket); + return await GetUserInformationAsync(tokenEndpointResponse ?? authorizationResponse, jwt, user, properties); } else { - var identity = (ClaimsIdentity)ticket.Principal.Identity; + var identity = (ClaimsIdentity)user.Identity; foreach (var action in Options.ClaimActions) { action.Run(null, identity, ClaimsIssuer); } } - return AuthenticateResult.Success(ticket); + return HandleRequestResult.Success(new AuthenticationTicket(user, properties, Scheme.Name)); } catch (Exception exception) { @@ -691,12 +687,12 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } var authenticationFailedContext = await RunAuthenticationFailedEventAsync(authorizationResponse, exception); - if (authenticationFailedContext.IsProcessingComplete(out result)) + if (authenticationFailedContext.Result != null) { - return result; + return authenticationFailedContext.Result; } - return AuthenticateResult.Fail(exception); + return HandleRequestResult.Fail(exception); } } @@ -765,21 +761,24 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// message that is being processed /// The . - /// authentication ticket with claims principal and identities - /// Authentication ticket with identity with additional claims, if any. - protected virtual async Task GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) + /// The claims principal and identities. + /// The authentication properties. + /// which is used to determine if the remote authentication was successful. + protected virtual async Task GetUserInformationAsync( + OpenIdConnectMessage message, JwtSecurityToken jwt, + ClaimsPrincipal principal, AuthenticationProperties properties) { var userInfoEndpoint = _configuration?.UserInfoEndpoint; if (string.IsNullOrEmpty(userInfoEndpoint)) { Logger.UserInfoEndpointNotSet(); - return AuthenticateResult.Success(ticket); + return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name)); } if (string.IsNullOrEmpty(message.AccessToken)) { Logger.AccessTokenNotAvailable(); - return AuthenticateResult.Success(ticket); + return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name)); } Logger.RetrievingClaims(); var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint); @@ -801,16 +800,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } else { - return AuthenticateResult.Fail("Unknown response type: " + contentType.MediaType); + return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType); } - var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); - AuthenticateResult result; - if (userInformationReceivedContext.IsProcessingComplete(out result)) + var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user); + if (userInformationReceivedContext.Result != null) { - return result; + return userInformationReceivedContext.Result; } - ticket = userInformationReceivedContext.Ticket; + principal = userInformationReceivedContext.Principal; + properties = userInformationReceivedContext.Properties; user = userInformationReceivedContext.User; Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() @@ -819,14 +818,14 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect ValidatedIdToken = jwt, }); - var identity = (ClaimsIdentity)ticket.Principal.Identity; + var identity = (ClaimsIdentity)principal.Identity; foreach (var action in Options.ClaimActions) { action.Run(user, identity, ClaimsIssuer); } - return AuthenticateResult.Success(ticket); + return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name)); } /// @@ -983,51 +982,54 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect private async Task RunMessageReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties) { Logger.MessageReceived(message.BuildRedirectUrl()); - var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options) + var context = new MessageReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = message, - Properties = properties, }; - await Events.MessageReceived(messageReceivedContext); - if (messageReceivedContext.HandledResponse) + await Events.MessageReceived(context); + if (context.Result != null) { - Logger.MessageReceivedContextHandledResponse(); - } - else if (messageReceivedContext.Skipped) - { - Logger.MessageReceivedContextSkipped(); + if (context.Result.Handled) + { + Logger.MessageReceivedContextHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.MessageReceivedContextSkipped(); + } } - return messageReceivedContext; + return context; } - private async Task RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt, string nonce) + private async Task RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, ClaimsPrincipal user, AuthenticationProperties properties, JwtSecurityToken jwt, string nonce) { - var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options) + var context = new TokenValidatedContext(Context, Scheme, Options, user, properties) { ProtocolMessage = authorizationResponse, TokenEndpointResponse = tokenEndpointResponse, - Properties = properties, - Ticket = ticket, SecurityToken = jwt, Nonce = nonce, }; - await Events.TokenValidated(tokenValidatedContext); - if (tokenValidatedContext.HandledResponse) + await Events.TokenValidated(context); + if (context.Result != null) { - Logger.TokenValidatedHandledResponse(); - } - else if (tokenValidatedContext.Skipped) - { - Logger.TokenValidatedSkipped(); + if (context.Result.Handled) + { + Logger.TokenValidatedHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.TokenValidatedSkipped(); + } } - return tokenValidatedContext; + return context; } - private async Task RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage authorizationResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt) + private async Task RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage authorizationResponse, ClaimsPrincipal user, AuthenticationProperties properties, JwtSecurityToken jwt) { Logger.AuthorizationCodeReceived(); @@ -1041,102 +1043,112 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect RedirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey] }; - var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Scheme, Options) + var context = new AuthorizationCodeReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = authorizationResponse, - Properties = properties, TokenEndpointRequest = tokenEndpointRequest, - Ticket = ticket, + Principal = user, JwtSecurityToken = jwt, - Backchannel = Backchannel, + Backchannel = Backchannel }; - await Events.AuthorizationCodeReceived(authorizationCodeReceivedContext); - if (authorizationCodeReceivedContext.HandledResponse) + await Events.AuthorizationCodeReceived(context); + if (context.Result != null) { - Logger.AuthorizationCodeReceivedContextHandledResponse(); - } - else if (authorizationCodeReceivedContext.Skipped) - { - Logger.AuthorizationCodeReceivedContextSkipped(); + if (context.Result.Handled) + { + Logger.AuthorizationCodeReceivedContextHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.AuthorizationCodeReceivedContextSkipped(); + } } - return authorizationCodeReceivedContext; + return context; } private async Task RunTokenResponseReceivedEventAsync( OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, - AuthenticationProperties properties, - AuthenticationTicket ticket) + ClaimsPrincipal user, + AuthenticationProperties properties) { Logger.TokenResponseReceived(); - var eventContext = new TokenResponseReceivedContext(Context, Scheme, Options, properties) + var context = new TokenResponseReceivedContext(Context, Scheme, Options, user, properties) { ProtocolMessage = message, TokenEndpointResponse = tokenEndpointResponse, - Ticket = ticket }; - await Events.TokenResponseReceived(eventContext); - if (eventContext.HandledResponse) + await Events.TokenResponseReceived(context); + if (context.Result != null) { - Logger.TokenResponseReceivedHandledResponse(); - } - else if (eventContext.Skipped) - { - Logger.TokenResponseReceivedSkipped(); + if (context.Result.Handled) + { + Logger.TokenResponseReceivedHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.TokenResponseReceivedSkipped(); + } } - return eventContext; + return context; } - private async Task RunUserInformationReceivedEventAsync(AuthenticationTicket ticket, OpenIdConnectMessage message, JObject user) + private async Task RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JObject user) { Logger.UserInformationReceived(user.ToString()); - var userInformationReceivedContext = new UserInformationReceivedContext(Context, Scheme, Options) + var context = new UserInformationReceivedContext(Context, Scheme, Options, principal, properties) { - Ticket = ticket, ProtocolMessage = message, User = user, }; - await Events.UserInformationReceived(userInformationReceivedContext); - if (userInformationReceivedContext.HandledResponse) + await Events.UserInformationReceived(context); + if (context.Result != null) { - Logger.UserInformationReceivedHandledResponse(); - } - else if (userInformationReceivedContext.Skipped) - { - Logger.UserInformationReceivedSkipped(); + if (context.Result.Handled) + { + Logger.UserInformationReceivedHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.UserInformationReceivedSkipped(); + } } - return userInformationReceivedContext; + return context; } private async Task RunAuthenticationFailedEventAsync(OpenIdConnectMessage message, Exception exception) { - var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) + var context = new AuthenticationFailedContext(Context, Scheme, Options) { ProtocolMessage = message, Exception = exception }; - await Events.AuthenticationFailed(authenticationFailedContext); - if (authenticationFailedContext.HandledResponse) + await Events.AuthenticationFailed(context); + if (context.Result != null) { - Logger.AuthenticationFailedContextHandledResponse(); - } - else if (authenticationFailedContext.Skipped) - { - Logger.AuthenticationFailedContextSkipped(); + if (context.Result.Handled) + { + Logger.AuthenticationFailedContextHandledResponse(); + } + else if (context.Result.Skipped) + { + Logger.AuthenticationFailedContextSkipped(); + } } - return authenticationFailedContext; + return context; } - private AuthenticationTicket ValidateToken(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt) + // Note this modifies properties if Options.UseTokenLifetime + private ClaimsPrincipal ValidateToken(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt) { if (!Options.SecurityTokenValidator.CanReadToken(idToken)) { @@ -1173,24 +1185,22 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateToken, idToken)); } - var ticket = new AuthenticationTicket(principal, properties, Scheme.Name); - if (Options.UseTokenLifetime) { var issued = validatedToken.ValidFrom; if (issued != DateTime.MinValue) { - ticket.Properties.IssuedUtc = issued; + properties.IssuedUtc = issued; } var expires = validatedToken.ValidTo; if (expires != DateTime.MinValue) { - ticket.Properties.ExpiresUtc = expires; + properties.ExpiresUtc = expires; } } - return ticket; + return principal; } /// diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs index a42f0eba33..8bcedaec27 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectOptions.cs @@ -4,13 +4,8 @@ using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/breakingchanges.netcore.json index 4ba3fb756a..0f50b12103 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/breakingchanges.netcore.json @@ -19,99 +19,45 @@ "TypeId": "public interface Microsoft.AspNetCore.Authentication.OpenIdConnect.IOpenIdConnectEvents : Microsoft.AspNetCore.Authentication.IRemoteAuthenticationEvents", "Kind": "Removal" }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthenticationFailedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthorizationCodeReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthorizationCodeReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthorizationCodeReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public Microsoft.AspNetCore.Builder.OpenIdConnectOptions get_Options()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.MessageReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.MessageReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.MessageReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.UserInformationReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.RedirectContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.RedirectContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenResponseReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenResponseReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.RemoteSignOutContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options, Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage message)", - "Kind": "Removal" - }, { "TypeId": "public static class Microsoft.AspNetCore.Builder.OpenIdConnectAppBuilderExtensions", "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseOpenIdConnectAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.OpenIdConnectOptions options)", "Kind": "Removal" - } + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthenticationFailedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.AuthorizationCodeReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext : Microsoft.AspNetCore.Authentication.BaseControlContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.MessageReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.RedirectContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.RemoteSignOutContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenResponseReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.OpenIdConnect.UserInformationReceivedContext : Microsoft.AspNetCore.Authentication.OpenIdConnect.BaseOpenIdConnectContext", + "Kind": "Removal" + } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/BaseTwitterContext.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/BaseTwitterContext.cs deleted file mode 100644 index b71b8655b7..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/BaseTwitterContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.Twitter -{ - /// - /// Base class for other Twitter contexts. - /// - public class BaseTwitterContext : BaseAuthenticationContext - { - /// - /// Initializes a - /// - /// The HTTP environment - /// The scheme data - /// The options for Twitter - /// The AuthenticationProperties - public BaseTwitterContext(HttpContext context, AuthenticationScheme scheme, TwitterOptions options, AuthenticationProperties properties) - : base(context, scheme.Name, properties) - { - Options = options; - } - - public TwitterOptions Options { get; } - - public AuthenticationScheme Scheme { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterCreatingTicketContext.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterCreatingTicketContext.cs index eaf704bcb9..67f28d5297 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterCreatingTicketContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterCreatingTicketContext.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter /// /// Contains information about the login session as well as the user . /// - public class TwitterCreatingTicketContext : BaseTwitterContext + public class TwitterCreatingTicketContext : ResultContext { /// /// Initializes a @@ -19,29 +19,33 @@ namespace Microsoft.AspNetCore.Authentication.Twitter /// The HTTP environment /// The scheme data /// The options for Twitter + /// The . + /// The . /// Twitter user ID /// Twitter screen name /// Twitter access token /// Twitter access token secret /// User details - /// AuthenticationProperties. public TwitterCreatingTicketContext( HttpContext context, AuthenticationScheme scheme, TwitterOptions options, + ClaimsPrincipal principal, AuthenticationProperties properties, string userId, string screenName, string accessToken, string accessTokenSecret, JObject user) - : base(context, scheme, options, properties) + : base(context, scheme, options) { UserId = userId; ScreenName = screenName; AccessToken = accessToken; AccessTokenSecret = accessTokenSecret; User = user ?? new JObject(); + Principal = principal; + Properties = properties; } /// @@ -69,10 +73,5 @@ namespace Microsoft.AspNetCore.Authentication.Twitter /// if it is not available. /// public JObject User { get; } - - /// - /// Gets the representing the user - /// - public ClaimsPrincipal Principal { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterEvents.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterEvents.cs index c079ebb14f..744c48c5fc 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterEvents.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter /// /// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked. /// - public Func OnRedirectToAuthorizationEndpoint { get; set; } = context => + public Func, Task> OnRedirectToAuthorizationEndpoint { get; set; } = context => { context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; @@ -36,6 +36,6 @@ namespace Microsoft.AspNetCore.Authentication.Twitter /// Called when a Challenge causes a redirect to authorize endpoint in the Twitter handler /// /// Contains redirect URI and of the challenge - public virtual Task RedirectToAuthorizationEndpoint(TwitterRedirectToAuthorizationEndpointContext context) => OnRedirectToAuthorizationEndpoint(context); + public virtual Task RedirectToAuthorizationEndpoint(RedirectContext context) => OnRedirectToAuthorizationEndpoint(context); } } diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterRedirectToAuthorizationEndpointContext.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterRedirectToAuthorizationEndpointContext.cs deleted file mode 100644 index fe181fe7b4..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/Events/TwitterRedirectToAuthorizationEndpointContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.Twitter -{ - /// - /// The Context passed when a Challenge causes a redirect to authorize endpoint in the Twitter handler. - /// - public class TwitterRedirectToAuthorizationEndpointContext : BaseTwitterContext - { - /// - /// Creates a new context object. - /// - /// The HTTP request context. - /// The scheme data - /// The Twitter handler options. - /// The authentication properties of the challenge. - /// The initial redirect URI. - public TwitterRedirectToAuthorizationEndpointContext(HttpContext context, AuthenticationScheme scheme, - - TwitterOptions options, AuthenticationProperties properties, string redirectUri) - : base(context, scheme, options, properties) - { - RedirectUri = redirectUri; - Properties = properties; - } - - /// - /// Gets the URI used for the redirect operation. - /// - public string RedirectUri { get; private set; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterExtensions.cs index d8b78398fc..e49244920e 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Twitter; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -10,6 +11,19 @@ namespace Microsoft.Extensions.DependencyInjection { public static class TwitterExtensions { + public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder) + => builder.AddTwitter(TwitterDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddTwitter(TwitterDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddTwitter(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, TwitterPostConfigureOptions>()); + return builder.AddRemoteScheme(authenticationScheme, authenticationScheme, configureOptions); + } + + // REMOVE below once callers have been updated. public static IServiceCollection AddTwitterAuthentication(this IServiceCollection services) => services.AddTwitterAuthentication(TwitterDefaults.AuthenticationScheme, _ => { }); diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs index baa4320d1b..7fcc01eee1 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter protected override Task CreateEventsAsync() => Task.FromResult(new TwitterEvents()); - protected override async Task HandleRemoteAuthenticateAsync() + protected override async Task HandleRemoteAuthenticateAsync() { AuthenticationProperties properties = null; var query = Request.Query; @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter if (requestToken == null) { - return AuthenticateResult.Fail("Invalid state cookie."); + return HandleRequestResult.Fail("Invalid state cookie."); } properties = requestToken.Properties; @@ -66,18 +66,18 @@ namespace Microsoft.AspNetCore.Authentication.Twitter var returnedToken = query["oauth_token"]; if (StringValues.IsNullOrEmpty(returnedToken)) { - return AuthenticateResult.Fail("Missing oauth_token"); + return HandleRequestResult.Fail("Missing oauth_token"); } if (!string.Equals(returnedToken, requestToken.Token, StringComparison.Ordinal)) { - return AuthenticateResult.Fail("Unmatched token"); + return HandleRequestResult.Fail("Unmatched token"); } var oauthVerifier = query["oauth_verifier"]; if (StringValues.IsNullOrEmpty(oauthVerifier)) { - return AuthenticateResult.Fail("Missing or blank oauth_verifier"); + return HandleRequestResult.Fail("Missing or blank oauth_verifier"); } var cookieOptions = new CookieOptions @@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter }); } - return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, accessToken, user)); + return HandleRequestResult.Success(await CreateTicketAsync(identity, properties, accessToken, user)); } protected virtual async Task CreateTicketAsync( @@ -127,18 +127,9 @@ namespace Microsoft.AspNetCore.Authentication.Twitter action.Run(user, identity, ClaimsIssuer); } - var context = new TwitterCreatingTicketContext(Context, Scheme, Options, properties, token.UserId, token.ScreenName, token.Token, token.TokenSecret, user) - { - Principal = new ClaimsPrincipal(identity) - }; - + var context = new TwitterCreatingTicketContext(Context, Scheme, Options, new ClaimsPrincipal(identity), properties, token.UserId, token.ScreenName, token.Token, token.TokenSecret, user); await Events.CreatingTicket(context); - if (context.Principal?.Identity == null) - { - return null; - } - return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } @@ -165,7 +156,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions); - var redirectContext = new TwitterRedirectToAuthorizationEndpointContext(Context, Scheme, Options, properties, twitterAuthenticationEndpoint); + var redirectContext = new RedirectContext(Context, Scheme, Options, properties, twitterAuthenticationEndpoint); await Events.RedirectToAuthorizationEndpoint(redirectContext); } diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication.Twitter/breakingchanges.netcore.json index 2023eb0b7a..6b84409111 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/breakingchanges.netcore.json @@ -19,31 +19,6 @@ "TypeId": "public interface Microsoft.AspNetCore.Authentication.Twitter.ITwitterEvents : Microsoft.AspNetCore.Authentication.IRemoteAuthenticationEvents", "Kind": "Removal" }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterRedirectToAuthorizationEndpointContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.TwitterOptions options, Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties properties, System.String redirectUri)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterRedirectToAuthorizationEndpointContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterCreatingTicketContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.TwitterOptions options, System.String userId, System.String screenName, System.String accessToken, System.String accessTokenSecret, Newtonsoft.Json.Linq.JObject user)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterCreatingTicketContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterCreatingTicketContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.RequestToken", "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", @@ -58,5 +33,13 @@ "TypeId": "public static class Microsoft.AspNetCore.Builder.TwitterAppBuilderExtensions", "MemberId": "public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseTwitterAuthentication(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.TwitterOptions options)", "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterCreatingTicketContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Twitter.TwitterRedirectToAuthorizationEndpointContext : Microsoft.AspNetCore.Authentication.Twitter.BaseTwitterContext", + "Kind": "Removal" } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationBuilder.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationBuilder.cs new file mode 100644 index 0000000000..c29bdeae29 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationBuilder.cs @@ -0,0 +1,103 @@ +// 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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Used to configure authentication + /// + public class AuthenticationBuilder + { + /// + /// Constructor. + /// + /// The services being configured. + public AuthenticationBuilder(IServiceCollection services) + => Services = services; + + /// + /// The services being configured. + /// + public virtual IServiceCollection Services { get; } + + /// + /// Adds a which can be used by . + /// + /// The type to configure the handler."/>. + /// The used to handle this scheme. + /// The name of this scheme. + /// The display name of this scheme. + /// Used to configure the scheme options. + /// The builder. + public virtual AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, Action configureOptions) + where TOptions : AuthenticationSchemeOptions, new() + where THandler : AuthenticationHandler + { + Services.Configure(o => + { + o.AddScheme(authenticationScheme, scheme => { + scheme.HandlerType = typeof(THandler); + scheme.DisplayName = displayName; + }); + }); + if (configureOptions != null) + { + Services.Configure(authenticationScheme, configureOptions); + } + Services.AddTransient(); + return this; + } + + /// + /// Adds a which can be used by . + /// + /// The type to configure the handler."/>. + /// The used to handle this scheme. + /// The name of this scheme. + /// Used to configure the scheme options. + /// The builder. + public virtual AuthenticationBuilder AddScheme(string authenticationScheme, Action configureOptions) + where TOptions : AuthenticationSchemeOptions, new() + where THandler : AuthenticationHandler + => AddScheme(authenticationScheme, displayName: null, configureOptions: configureOptions); + + /// + /// Adds a based that supports remote authentication + /// which can be used by . + /// + /// The type to configure the handler."/>. + /// The used to handle this scheme. + /// The name of this scheme. + /// The display name of this scheme. + /// Used to configure the scheme options. + /// The builder. + public virtual AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) + where TOptions : RemoteAuthenticationOptions, new() + where THandler : RemoteAuthenticationHandler + { + Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureSignInScheme>()); + return AddScheme(authenticationScheme, displayName, configureOptions: configureOptions); + } + + // Used to ensure that there's always a default data protection provider + private class EnsureSignInScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + { + private readonly AuthenticationOptions _authOptions; + + public EnsureSignInScheme(IOptions authOptions) + { + _authOptions = authOptions.Value; + } + + public void PostConfigure(string name, TOptions options) + { + options.SignInScheme = options.SignInScheme ?? _authOptions.DefaultSignInScheme; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs index 788df7e19c..aeb70cb0de 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs @@ -178,35 +178,6 @@ namespace Microsoft.AspNetCore.Authentication protected abstract Task HandleAuthenticateAsync(); - public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - properties = properties ?? new AuthenticationProperties(); - await HandleSignInAsync(user, properties); - Logger.AuthenticationSchemeSignedIn(Scheme.Name); - } - - protected virtual Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - return Task.CompletedTask; - } - - public async Task SignOutAsync(AuthenticationProperties properties) - { - properties = properties ?? new AuthenticationProperties(); - await HandleSignOutAsync(properties); - Logger.AuthenticationSchemeSignedOut(Scheme.Name); - } - - protected virtual Task HandleSignOutAsync(AuthenticationProperties properties) - { - return Task.CompletedTask; - } - /// /// Override this method to handle Forbid. /// diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs index 7ebc979f1b..28291f4196 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class AuthenticationServiceCollectionExtensions { - public static IServiceCollection AddAuthentication(this IServiceCollection services) + public static AuthenticationBuilder AddAuthentication(this IServiceCollection services) { if (services == null) { @@ -24,10 +24,10 @@ namespace Microsoft.Extensions.DependencyInjection services.AddDataProtection(); services.AddWebEncoders(); services.TryAddSingleton(); - return services; + return new AuthenticationBuilder(services); } - public static IServiceCollection AddAuthentication(this IServiceCollection services, Action configureOptions) { + public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); @@ -38,11 +38,12 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(configureOptions)); } - services.AddAuthentication(); + var builder = services.AddAuthentication(); services.Configure(configureOptions); - return services; + return builder; } + // REMOVE below once callers have been updated public static IServiceCollection AddScheme(this IServiceCollection services, string authenticationScheme, string displayName, Action configureScheme, Action configureOptions) where TOptions : AuthenticationSchemeOptions, new() where THandler : AuthenticationHandler diff --git a/src/Microsoft.AspNetCore.Authentication/Events/BaseAuthenticationContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/BaseAuthenticationContext.cs deleted file mode 100644 index cfe5809c5a..0000000000 --- a/src/Microsoft.AspNetCore.Authentication/Events/BaseAuthenticationContext.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication -{ - /// - /// Base context for authentication. - /// - public abstract class BaseAuthenticationContext : BaseContext - { - /// - /// Constructor. - /// - /// The context. - /// The name of the scheme. - /// The properties. - protected BaseAuthenticationContext(HttpContext context, string authenticationScheme, AuthenticationProperties properties) : base(context) - { - if (string.IsNullOrEmpty(authenticationScheme)) - { - throw new ArgumentException(nameof(authenticationScheme)); - } - - AuthenticationScheme = authenticationScheme; - Properties = properties ?? new AuthenticationProperties(); - } - - /// - /// The name of the scheme. - /// - public string AuthenticationScheme { get; } - - /// - /// Contains the extra meta-data arriving with the authentication. May be altered. - /// - public AuthenticationProperties Properties { get; protected set; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication/Events/BaseContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/BaseContext.cs index 3d65f0dd75..915fc2377f 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/BaseContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/BaseContext.cs @@ -9,22 +9,44 @@ namespace Microsoft.AspNetCore.Authentication /// /// Base class used by other context classes. /// - public abstract class BaseContext + public abstract class BaseContext where TOptions : AuthenticationSchemeOptions { /// /// Constructor. /// - /// The request context. - protected BaseContext(HttpContext context) + /// The context. + /// The authentication scheme. + /// The authentication options associated with the scheme. + protected BaseContext(HttpContext context, AuthenticationScheme scheme, TOptions options) { if (context == null) { throw new ArgumentNullException(nameof(context)); } + if (scheme == null) + { + throw new ArgumentNullException(nameof(scheme)); + } + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } HttpContext = context; + Scheme = scheme; + Options = options; } + /// + /// The authentication scheme. + /// + public AuthenticationScheme Scheme { get; } + + /// + /// Gets the authentication options associated with the scheme. + /// + public TOptions Options { get; } + /// /// The context. /// @@ -33,17 +55,11 @@ namespace Microsoft.AspNetCore.Authentication /// /// The request. /// - public HttpRequest Request - { - get { return HttpContext.Request; } - } + public HttpRequest Request => HttpContext.Request; /// /// The response. /// - public HttpResponse Response - { - get { return HttpContext.Response; } - } + public HttpResponse Response => HttpContext.Response; } } diff --git a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs deleted file mode 100644 index fa582a3040..0000000000 --- a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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 Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication -{ - public class BaseControlContext : BaseContext - { - protected BaseControlContext(HttpContext context) : base(context) - { - } - - public EventResultState State { get; set; } - - public bool HandledResponse - { - get { return State == EventResultState.HandledResponse; } - } - - public bool Skipped - { - get { return State == EventResultState.Skipped; } - } - - /// - /// Discontinue all processing for this request and return to the client. - /// The caller is responsible for generating the full response. - /// Set the to trigger SignIn. - /// - public void HandleResponse() - { - State = EventResultState.HandledResponse; - } - - /// - /// Discontinue processing the request in the current handler. - /// SignIn will not be called. - /// - public void Skip() - { - State = EventResultState.Skipped; - } - - /// - /// Gets or set the to return if this event signals it handled the event. - /// - public AuthenticationTicket Ticket { get; set; } - - /// - /// Returns true if the handler should be done processing. - /// - /// The result. - /// Whether the handler should be done processing. - public bool IsProcessingComplete(out AuthenticateResult result) - { - if (HandledResponse) - { - if (Ticket == null) - { - result = AuthenticateResult.Handle(); - } - else - { - result = AuthenticateResult.Success(Ticket); - } - return true; - } - else if (Skipped) - { - result = AuthenticateResult.None(); - return true; - } - result = null; - return false; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/Events/EventResultState.cs b/src/Microsoft.AspNetCore.Authentication/Events/EventResultState.cs deleted file mode 100644 index dad4c40fec..0000000000 --- a/src/Microsoft.AspNetCore.Authentication/Events/EventResultState.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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. - -namespace Microsoft.AspNetCore.Authentication -{ - public enum EventResultState - { - /// - /// Continue with normal processing. - /// - Continue, - - /// - /// Discontinue processing the request. - /// - Skipped, - - /// - /// Discontinue all processing for this request. - /// - HandledResponse - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/Events/HandleRequestContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/HandleRequestContext.cs new file mode 100644 index 0000000000..52dd9ce12f --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/Events/HandleRequestContext.cs @@ -0,0 +1,32 @@ +// 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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + public class HandleRequestContext : BaseContext where TOptions : AuthenticationSchemeOptions + { + protected HandleRequestContext( + HttpContext context, + AuthenticationScheme scheme, + TOptions options) + : base(context, scheme, options) { } + + /// + /// The which is used by the handler. + /// + public HandleRequestResult Result { get; protected set; } + + /// + /// Discontinue all processing for this request and return to the client. + /// The caller is responsible for generating the full response. + /// + public void HandleResponse() => Result = HandleRequestResult.Handle(); + + /// + /// Discontinue processing the request in the current handler. + /// + public void SkipHandler() => Result = HandleRequestResult.SkipHandler(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/Events/PrincipalContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/PrincipalContext.cs new file mode 100644 index 0000000000..8bf40760a1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/Events/PrincipalContext.cs @@ -0,0 +1,30 @@ +// 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; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Base context for authentication events which deal with a ClaimsPrincipal. + /// + public abstract class PrincipalContext : PropertiesContext where TOptions : AuthenticationSchemeOptions + { + /// + /// Constructor. + /// + /// The context. + /// The authentication scheme. + /// The authentication options associated with the scheme. + /// The authentication properties. + protected PrincipalContext(HttpContext context, AuthenticationScheme scheme, TOptions options, AuthenticationProperties properties) + : base(context, scheme, options, properties) { } + + /// + /// Gets the containing the user claims. + /// + public virtual ClaimsPrincipal Principal { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication/Events/PropertiesContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/PropertiesContext.cs new file mode 100644 index 0000000000..f1730d0d7f --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/Events/PropertiesContext.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Base context for authentication events which contain . + /// + public abstract class PropertiesContext : BaseContext where TOptions : AuthenticationSchemeOptions + { + /// + /// Constructor. + /// + /// The context. + /// The authentication scheme. + /// The authentication options associated with the scheme. + /// The authentication properties. + protected PropertiesContext(HttpContext context, AuthenticationScheme scheme, TOptions options, AuthenticationProperties properties) + : base(context, scheme, options) + { + Properties = properties ?? new AuthenticationProperties(); + } + + /// + /// Gets or sets the . + /// + public virtual AuthenticationProperties Properties { get; protected set; } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieRedirectContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/RedirectContext.cs similarity index 62% rename from src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieRedirectContext.cs rename to src/Microsoft.AspNetCore.Authentication/Events/RedirectContext.cs index e4259d181e..dac24cafa6 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieRedirectContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/RedirectContext.cs @@ -1,28 +1,32 @@ // 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 Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; -namespace Microsoft.AspNetCore.Authentication.Cookies +namespace Microsoft.AspNetCore.Authentication { /// - /// Context passed when a Challenge, SignIn, or SignOut causes a redirect in the cookie handler + /// Context passed for redirect events. /// - public class CookieRedirectContext : BaseCookieContext + public class RedirectContext : PropertiesContext where TOptions : AuthenticationSchemeOptions { /// /// Creates a new context object. /// /// The HTTP request context /// The scheme data - /// The cookie handler options + /// The handler options /// The initial redirect URI /// The . - public CookieRedirectContext(HttpContext context, AuthenticationScheme scheme, CookieAuthenticationOptions options, string redirectUri, AuthenticationProperties properties) + public RedirectContext( + HttpContext context, + AuthenticationScheme scheme, + TOptions options, + AuthenticationProperties properties, + string redirectUri) : base(context, scheme, options, properties) { + Properties = properties; RedirectUri = redirectUri; } diff --git a/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationContext.cs new file mode 100644 index 0000000000..b7a0168798 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationContext.cs @@ -0,0 +1,49 @@ +// 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; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Base context for remote authentication. + /// + public abstract class RemoteAuthenticationContext : HandleRequestContext where TOptions : AuthenticationSchemeOptions + { + /// + /// Constructor. + /// + /// The context. + /// The authentication scheme. + /// The authentication options associated with the scheme. + /// The authentication properties. + protected RemoteAuthenticationContext( + HttpContext context, + AuthenticationScheme scheme, + TOptions options, + AuthenticationProperties properties) + : base(context, scheme, options) + => Properties = properties ?? new AuthenticationProperties(); + + /// + /// Gets the containing the user claims. + /// + public ClaimsPrincipal Principal { get; set; } + + /// + /// Gets or sets the . + /// + public virtual AuthenticationProperties Properties { get; set; } + + /// + /// Calls success creating a ticket with the and . + /// + public void Success() => Result = HandleRequestResult.Success(new AuthenticationTicket(Principal, Properties, Scheme.Name)); + + public void Fail(Exception failure) => Result = HandleRequestResult.Fail(failure); + + public void Fail(string failureMessage) => Result = HandleRequestResult.Fail(failureMessage); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs b/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs index 83a6507d42..ca0f4a5c01 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs @@ -8,14 +8,14 @@ namespace Microsoft.AspNetCore.Authentication { public class RemoteAuthenticationEvents { - public Func OnRemoteFailure { get; set; } = context => Task.CompletedTask; + public Func OnRemoteFailure { get; set; } = context => Task.CompletedTask; public Func OnTicketReceived { get; set; } = context => Task.CompletedTask; /// /// Invoked when there is a remote failure /// - public virtual Task RemoteFailure(FailureContext context) => OnRemoteFailure(context); + public virtual Task RemoteFailure(RemoteFailureContext context) => OnRemoteFailure(context); /// /// Invoked after the remote ticket has been received. diff --git a/src/Microsoft.AspNetCore.Authentication/Events/FailureContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/RemoteFailureContext.cs similarity index 64% rename from src/Microsoft.AspNetCore.Authentication/Events/FailureContext.cs rename to src/Microsoft.AspNetCore.Authentication/Events/RemoteFailureContext.cs index 5d2b30f130..becdfb5439 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/FailureContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/RemoteFailureContext.cs @@ -9,10 +9,14 @@ namespace Microsoft.AspNetCore.Authentication /// /// Provides failure context information to handler providers. /// - public class FailureContext : BaseControlContext + public class RemoteFailureContext : HandleRequestContext { - public FailureContext(HttpContext context, Exception failure) - : base(context) + public RemoteFailureContext( + HttpContext context, + AuthenticationScheme scheme, + RemoteAuthenticationOptions options, + Exception failure) + : base(context, scheme, options) { Failure = failure; } diff --git a/src/Microsoft.AspNetCore.Authentication/Events/ResultContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/ResultContext.cs new file mode 100644 index 0000000000..12b21f4bf6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/Events/ResultContext.cs @@ -0,0 +1,65 @@ +// 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; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Base context for events that produce AuthenticateResults. + /// + public abstract class ResultContext : BaseContext where TOptions : AuthenticationSchemeOptions + { + /// + /// Constructor. + /// + /// The context. + /// The authentication scheme. + /// The authentication options associated with the scheme. + protected ResultContext(HttpContext context, AuthenticationScheme scheme, TOptions options) + : base(context, scheme, options) { } + + /// + /// Gets or sets the containing the user claims. + /// + public ClaimsPrincipal Principal { get; set; } + + private AuthenticationProperties _properties; + /// + /// Gets or sets the . + /// + public AuthenticationProperties Properties { + get => _properties ?? (_properties = new AuthenticationProperties()); + set => _properties = value; + } + + /// + /// Gets the result. + /// + public AuthenticateResult Result { get; private set; } + + /// + /// Calls success creating a ticket with the and . + /// + public void Success() => Result = HandleRequestResult.Success(new AuthenticationTicket(Principal, Properties, Scheme.Name)); + + /// + /// Indicates that there was no information returned for this authentication scheme. + /// + public void NoResult() => Result = AuthenticateResult.NoResult(); + + /// + /// Indicates that there was a failure during authentication. + /// + /// + public void Fail(Exception failure) => Result = AuthenticateResult.Fail(failure); + + /// + /// Indicates that there was a failure during authentication. + /// + /// + public void Fail(string failureMessage) => Result = AuthenticateResult.Fail(failureMessage); + } +} diff --git a/src/Microsoft.AspNetCore.Authentication/Events/TicketReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/TicketReceivedContext.cs index c0797ea9cc..51b77a37fa 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/TicketReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/TicketReceivedContext.cs @@ -3,30 +3,21 @@ using System.Security.Claims; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; namespace Microsoft.AspNetCore.Authentication { /// /// Provides context information to handler providers. /// - public class TicketReceivedContext : BaseControlContext + public class TicketReceivedContext : RemoteAuthenticationContext { - public TicketReceivedContext(HttpContext context, RemoteAuthenticationOptions options, AuthenticationTicket ticket) - : base(context) - { - Options = options; - Ticket = ticket; - if (ticket != null) - { - Principal = ticket.Principal; - Properties = ticket.Properties; - } - } - - public ClaimsPrincipal Principal { get; set; } - public AuthenticationProperties Properties { get; set; } - public RemoteAuthenticationOptions Options { get; set; } + public TicketReceivedContext( + HttpContext context, + AuthenticationScheme scheme, + RemoteAuthenticationOptions options, + AuthenticationTicket ticket) + : base(context, scheme, options, ticket?.Properties) + => Principal = ticket?.Principal; public string ReturnUri { get; set; } } diff --git a/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs index 49fc8db050..ed15e61904 100644 --- a/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs @@ -9,14 +9,8 @@ namespace Microsoft.Extensions.Logging { private static Action _authSchemeAuthenticated; private static Action _authSchemeNotAuthenticated; - private static Action _authSchemeNotAuthenticatedWithFailure; - private static Action _authSchemeSignedIn; - private static Action _authSchemeSignedOut; private static Action _authSchemeChallenged; private static Action _authSchemeForbidden; - private static Action _userAuthorizationFailed; - private static Action _userAuthorizationSucceeded; - private static Action _userPrincipalMerged; private static Action _remoteAuthenticationError; private static Action _signInHandled; private static Action _signInSkipped; @@ -26,18 +20,6 @@ namespace Microsoft.Extensions.Logging static LoggingExtensions() { - _userAuthorizationSucceeded = LoggerMessage.Define( - eventId: 1, - logLevel: LogLevel.Information, - formatString: "Authorization was successful for user: {UserName}."); - _userAuthorizationFailed = LoggerMessage.Define( - eventId: 2, - logLevel: LogLevel.Information, - formatString: "Authorization failed for user: {UserName}."); - _userPrincipalMerged = LoggerMessage.Define( - eventId: 3, - logLevel: LogLevel.Information, - formatString: "HttpContext.User merged via AutomaticAuthentication from authenticationScheme: {AuthenticationScheme}."); _remoteAuthenticationError = LoggerMessage.Define( eventId: 4, logLevel: LogLevel.Information, @@ -50,10 +32,6 @@ namespace Microsoft.Extensions.Logging eventId: 6, logLevel: LogLevel.Debug, formatString: "The SigningIn event returned Skipped."); - _authSchemeNotAuthenticatedWithFailure = LoggerMessage.Define( - eventId: 7, - logLevel: LogLevel.Information, - formatString: "{AuthenticationScheme} was not authenticated. Failure message: {FailureMessage}"); _authSchemeAuthenticated = LoggerMessage.Define( eventId: 8, logLevel: LogLevel.Information, @@ -62,14 +40,6 @@ namespace Microsoft.Extensions.Logging eventId: 9, logLevel: LogLevel.Debug, formatString: "AuthenticationScheme: {AuthenticationScheme} was not authenticated."); - _authSchemeSignedIn = LoggerMessage.Define( - eventId: 10, - logLevel: LogLevel.Information, - formatString: "AuthenticationScheme: {AuthenticationScheme} signed in."); - _authSchemeSignedOut = LoggerMessage.Define( - eventId: 11, - logLevel: LogLevel.Information, - formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); _authSchemeChallenged = LoggerMessage.Define( eventId: 12, logLevel: LogLevel.Information, @@ -102,21 +72,6 @@ namespace Microsoft.Extensions.Logging _authSchemeNotAuthenticated(logger, authenticationScheme, null); } - public static void AuthenticationSchemeNotAuthenticatedWithFailure(this ILogger logger, string authenticationScheme, string failureMessage) - { - _authSchemeNotAuthenticatedWithFailure(logger, authenticationScheme, failureMessage, null); - } - - public static void AuthenticationSchemeSignedIn(this ILogger logger, string authenticationScheme) - { - _authSchemeSignedIn(logger, authenticationScheme, null); - } - - public static void AuthenticationSchemeSignedOut(this ILogger logger, string authenticationScheme) - { - _authSchemeSignedOut(logger, authenticationScheme, null); - } - public static void AuthenticationSchemeChallenged(this ILogger logger, string authenticationScheme) { _authSchemeChallenged(logger, authenticationScheme, null); @@ -127,21 +82,6 @@ namespace Microsoft.Extensions.Logging _authSchemeForbidden(logger, authenticationScheme, null); } - public static void UserAuthorizationSucceeded(this ILogger logger, string userName) - { - _userAuthorizationSucceeded(logger, userName, null); - } - - public static void UserAuthorizationFailed(this ILogger logger, string userName) - { - _userAuthorizationFailed(logger, userName, null); - } - - public static void UserPrinicpalMerged(this ILogger logger, string authenticationScheme) - { - _userPrincipalMerged(logger, authenticationScheme, null); - } - public static void RemoteAuthenticationError(this ILogger logger, string errorMessage) { _remoteAuthenticationError(logger, errorMessage, null); diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs index 69c926cc0f..62213a171b 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Security.Claims; using System.Security.Cryptography; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -35,19 +34,13 @@ namespace Microsoft.AspNetCore.Authentication } protected RemoteAuthenticationHandler(IOptionsSnapshot options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) - : base(options, logger, encoder, clock) - { - } + : base(options, logger, encoder, clock) { } protected override Task CreateEventsAsync() - { - return Task.FromResult(new RemoteAuthenticationEvents()); - } + => Task.FromResult(new RemoteAuthenticationEvents()); public virtual Task ShouldHandleRequestAsync() - { - return Task.FromResult(Options.CallbackPath == Request.Path); - } + => Task.FromResult(Options.CallbackPath == Request.Path); public virtual async Task HandleRequestAsync() { @@ -69,7 +62,7 @@ namespace Microsoft.AspNetCore.Authentication { return true; } - else if (authResult.Nothing) + else if (authResult.Skipped || authResult.None) { return false; } @@ -89,25 +82,28 @@ namespace Microsoft.AspNetCore.Authentication if (exception != null) { Logger.RemoteAuthenticationError(exception.Message); - var errorContext = new FailureContext(Context, exception); + var errorContext = new RemoteFailureContext(Context, Scheme, Options, exception); await Events.RemoteFailure(errorContext); - if (errorContext.HandledResponse) + if (errorContext.Result != null) { - return true; - } - else if (errorContext.Skipped) - { - return false; + if (errorContext.Result.Handled) + { + return true; + } + else if (errorContext.Result.Skipped) + { + return false; + } } - throw new AggregateException("Unhandled remote failure.", exception); + throw exception; } // We have a ticket if we get here - var ticketContext = new TicketReceivedContext(Context, Options, ticket) + var ticketContext = new TicketReceivedContext(Context, Scheme, Options, ticket) { - ReturnUri = ticket.Properties.RedirectUri, + ReturnUri = ticket.Properties.RedirectUri }; // REVIEW: is this safe or good? ticket.Properties.RedirectUri = null; @@ -117,15 +113,18 @@ namespace Microsoft.AspNetCore.Authentication await Events.TicketReceived(ticketContext); - if (ticketContext.HandledResponse) + if (ticketContext.Result != null) { - Logger.SigninHandled(); - return true; - } - else if (ticketContext.Skipped) - { - Logger.SigninSkipped(); - return false; + if (ticketContext.Result.Handled) + { + Logger.SigninHandled(); + return true; + } + else if (ticketContext.Result.Skipped) + { + Logger.SigninSkipped(); + return false; + } } await Context.SignInAsync(SignInScheme, ticketContext.Principal, ticketContext.Properties); @@ -145,7 +144,7 @@ namespace Microsoft.AspNetCore.Authentication /// /// The method process the request on the endpoint defined by CallbackPath. /// - protected abstract Task HandleRemoteAuthenticateAsync(); + protected abstract Task HandleRemoteAuthenticateAsync(); protected override async Task HandleAuthenticateAsync() { @@ -174,20 +173,8 @@ namespace Microsoft.AspNetCore.Authentication return AuthenticateResult.Fail("Remote authentication does not directly support AuthenticateAsync"); } - protected override Task HandleSignOutAsync(AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - - protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - protected override Task HandleForbiddenAsync(AuthenticationProperties properties) - { - return Context.ForbidAsync(SignInScheme); - } + => Context.ForbidAsync(SignInScheme); protected virtual void GenerateCorrelationId(AuthenticationProperties properties) { diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs index 066ca963a3..a5f0bb44b4 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs @@ -23,11 +23,6 @@ namespace Microsoft.AspNetCore.Authentication { throw new ArgumentException(Resources.FormatException_OptionMustBeProvided(nameof(CallbackPath)), nameof(CallbackPath)); } - - if (string.IsNullOrEmpty(SignInScheme)) - { - throw new ArgumentException(Resources.FormatException_OptionMustBeProvided(nameof(SignInScheme)), nameof(SignInScheme)); - } } /// diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationResult.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationResult.cs new file mode 100644 index 0000000000..8bcd2be01d --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationResult.cs @@ -0,0 +1,78 @@ +// 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; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Contains the result of an Authenticate call + /// + public class HandleRequestResult : AuthenticateResult + { + /// + /// Indicates that stage of authentication was directly handled by + /// user intervention and no further processing should be attempted. + /// + public bool Handled { get; private set; } + + /// + /// Indicates that the default authentication logic should be + /// skipped and that the rest of the pipeline should be invoked. + /// + public bool Skipped { get; private set; } + + /// + /// Indicates that authentication was successful. + /// + /// The ticket representing the authentication result. + /// The result. + public static new HandleRequestResult Success(AuthenticationTicket ticket) + { + if (ticket == null) + { + throw new ArgumentNullException(nameof(ticket)); + } + return new HandleRequestResult() { Ticket = ticket }; + } + + /// + /// Indicates that there was a failure during authentication. + /// + /// The failure exception. + /// The result. + public static new HandleRequestResult Fail(Exception failure) + { + return new HandleRequestResult() { Failure = failure }; + } + + /// + /// Indicates that there was a failure during authentication. + /// + /// The failure message. + /// The result. + public static new HandleRequestResult Fail(string failureMessage) + { + return new HandleRequestResult() { Failure = new Exception(failureMessage) }; + } + + /// + /// Discontinue all processing for this request and return to the client. + /// The caller is responsible for generating the full response. + /// + /// The result. + public static HandleRequestResult Handle() + { + return new HandleRequestResult() { Handled = true }; + } + + /// + /// Discontinue processing the request in the current handler. + /// + /// The result. + public static HandleRequestResult SkipHandler() + { + return new HandleRequestResult() { Skipped = true }; + } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json index 0eb0bfaf5f..81dc1133e2 100644 --- a/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json +++ b/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json @@ -83,44 +83,34 @@ "TypeId": "public static class Microsoft.AspNetCore.Builder.ClaimsTransformationAppBuilderExtensions", "Kind": "Removal" }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public .ctor(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Builder.RemoteAuthenticationOptions options, Microsoft.AspNetCore.Authentication.AuthenticationTicket ticket)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public Microsoft.AspNetCore.Builder.RemoteAuthenticationOptions get_Options()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties get_Properties()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public System.Void set_Options(Microsoft.AspNetCore.Builder.RemoteAuthenticationOptions value)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", - "MemberId": "public System.Void set_Properties(Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties value)", - "Kind": "Removal" - }, { "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthentication(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions)", "Kind": "Removal" }, { - "TypeId": "public class Microsoft.AspNetCore.Authentication.BaseControlContext : Microsoft.AspNetCore.Authentication.BaseContext", - "MemberId": "public System.Boolean CheckEventResult(out Microsoft.AspNetCore.Authentication.AuthenticateResult result)", + "TypeId": "public abstract class Microsoft.AspNetCore.Authentication.BaseContext", "Kind": "Removal" }, { "TypeId": "public class Microsoft.AspNetCore.Authentication.BaseControlContext : Microsoft.AspNetCore.Authentication.BaseContext", - "MemberId": "public System.Void SkipToNextMiddleware()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.FailureContext : Microsoft.AspNetCore.Authentication.BaseControlContext", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.TicketReceivedContext : Microsoft.AspNetCore.Authentication.BaseControlContext", + "Kind": "Removal" + }, + { + "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", + "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthentication(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)", + "Kind": "Removal" + }, + { + "TypeId": "public enum Microsoft.AspNetCore.Authentication.EventResultState", "Kind": "Removal" } ] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authorization.Policy/PolicyEvaluator.cs b/src/Microsoft.AspNetCore.Authorization.Policy/PolicyEvaluator.cs index 60f291f671..f93c2d92a3 100644 --- a/src/Microsoft.AspNetCore.Authorization.Policy/PolicyEvaluator.cs +++ b/src/Microsoft.AspNetCore.Authorization.Policy/PolicyEvaluator.cs @@ -52,13 +52,13 @@ namespace Microsoft.AspNetCore.Authorization.Policy else { context.User = new ClaimsPrincipal(new ClaimsIdentity()); - return AuthenticateResult.None(); + return AuthenticateResult.NoResult(); } } return (context.User?.Identity?.IsAuthenticated ?? false) ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) - : AuthenticateResult.None(); + : AuthenticateResult.NoResult(); } /// diff --git a/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs index 677120e4be..7dade96eec 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http; @@ -29,7 +30,8 @@ namespace Microsoft.AspNetCore.Authentication.Cookies [Fact] public async Task VerifySchemeDefaults() { - var services = new ServiceCollection().AddCookieAuthentication(); + var services = new ServiceCollection(); + services.AddAuthentication().AddCookie(); var sp = services.BuildServiceProvider(); var schemeProvider = sp.GetRequiredService(); var scheme = await schemeProvider.GetSchemeAsync(CookieAuthenticationDefaults.AuthenticationScheme); @@ -124,7 +126,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies [Fact] public async Task SignInCausesDefaultCookieToBeCreated() { - var server = CreateServerWithServices(s => s.AddCookieAuthentication(o => + var server = CreateServerWithServices(s => s.AddAuthentication().AddCookie(o => { o.LoginPath = new PathString("/login"); o.CookieName = "TestCookie"; @@ -772,7 +774,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies } app.Map("/login", signoutApp => signoutApp.Run(context => context.ChallengeAsync("Cookies", new AuthenticationProperties() { RedirectUri = "/" }))); }) - .ConfigureServices(s => s.AddCookieAuthentication(o => o.LoginPath = new PathString("/page"))); + .ConfigureServices(s => s.AddAuthentication().AddCookie(o => o.LoginPath = new PathString("/page"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/login"); @@ -803,7 +805,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies await Assert.ThrowsAsync(() => context.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme)); }); }) - .ConfigureServices(services => services.AddCookieAuthentication()); + .ConfigureServices(services => services.AddAuthentication().AddCookie()); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com"); @@ -821,7 +823,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies }) .ConfigureServices(services => { - services.AddCookieAuthentication(); + services.AddAuthentication().AddCookie(); services.Configure(CookieAuthenticationDefaults.AuthenticationScheme, o => o.CookieName = "One"); }); @@ -844,7 +846,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies }) .ConfigureServices(services => { - services.AddCookieAuthentication("Cookie1"); + services.AddAuthentication().AddCookie("Cookie1"); services.Configure("Cookie1", o => o.CookieName = "One"); }); @@ -866,7 +868,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.SignInAsync("Cookies", new ClaimsPrincipal()))); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.LoginPath = new PathString("/login"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.LoginPath = new PathString("/login"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/notlogin?ReturnUrl=%2Fpage"); @@ -883,7 +885,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies app.UseAuthentication(); app.Map("/login", signoutApp => signoutApp.Run(context => context.SignInAsync("Cookies", new ClaimsPrincipal()))); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.LoginPath = new PathString("/login"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.LoginPath = new PathString("/login"))); var server = new TestServer(builder); @@ -905,7 +907,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies app.UseAuthentication(); app.Map("/notlogout", signoutApp => signoutApp.Run(context => context.SignOutAsync("Cookies"))); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.LogoutPath = new PathString("/logout"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.LogoutPath = new PathString("/logout"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/notlogout?ReturnUrl=%2Fpage"); @@ -922,7 +924,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies app.UseAuthentication(); app.Map("/logout", signoutApp => signoutApp.Run(context => context.SignOutAsync("Cookies"))); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.LogoutPath = new PathString("/logout"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.LogoutPath = new PathString("/logout"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/logout?ReturnUrl=%2Fpage"); @@ -943,7 +945,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies app.UseAuthentication(); app.Map("/forbid", signoutApp => signoutApp.Run(context => context.ForbidAsync("Cookies"))); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.AccessDeniedPath = new PathString("/denied"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.AccessDeniedPath = new PathString("/denied"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/forbid"); @@ -963,7 +965,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies map.UseAuthentication(); map.Map("/login", signoutApp => signoutApp.Run(context => context.ChallengeAsync("Cookies", new AuthenticationProperties() { RedirectUri = "/" }))); })) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.LoginPath = new PathString("/page"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.LoginPath = new PathString("/page"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/base/login"); @@ -1073,7 +1075,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies map.UseAuthentication(); map.Map("/forbid", signoutApp => signoutApp.Run(context => context.ForbidAsync("Cookies"))); })) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.AccessDeniedPath = new PathString("/denied"))); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.AccessDeniedPath = new PathString("/denied"))); var server = new TestServer(builder); var transaction = await server.SendAsync("http://example.com/base/forbid"); @@ -1097,7 +1099,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))), new AuthenticationProperties())); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => { o.TicketDataFormat = new TicketDataFormat(dp); o.CookieName = "Cookie"; @@ -1117,7 +1119,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies Describe(context.Response, result); }); }) - .ConfigureServices(services => services.AddCookieAuthentication("Cookies", o => + .ConfigureServices(services => services.AddAuthentication().AddCookie("Cookies", o => { o.CookieName = "Cookie"; o.TicketDataFormat = new TicketDataFormat(dp); @@ -1132,7 +1134,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies public async Task NullExpiresUtcPropertyIsGuarded() { var builder = new WebHostBuilder() - .ConfigureServices(services => services.AddCookieAuthentication(o => + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => { o.Events = new CookieAuthenticationEvents { @@ -1229,7 +1231,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies => CreateServerWithServices(s => { s.AddSingleton(_clock); - s.AddCookieAuthentication(configureOptions); + s.AddAuthentication().AddCookie(configureOptions); s.AddSingleton(); }, testpath, baseAddress); @@ -1281,6 +1283,15 @@ namespace Microsoft.AspNetCore.Authentication.Cookies { await testpath(context); } + else if (req.Path == new PathString("/checkforerrors")) + { + var result = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // this used to be "Automatic" + if (result.Failure != null) + { + throw new Exception("Failed to authenticate", result.Failure); + } + return; + } else { await next(); diff --git a/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs index b463ef8911..e6b5574df1 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs @@ -85,9 +85,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook { options.DefaultSignInScheme = "External"; options.DefaultAuthenticateScheme = "External"; - }); - services.AddCookieAuthentication("External", o => { }); - services.AddFacebookAuthentication(o => + }) + .AddCookie("External", o => { }) + .AddFacebook(o => { o.AppId = "Test App Id"; o.AppSecret = "Test App Secret"; @@ -123,12 +123,12 @@ namespace Microsoft.AspNetCore.Authentication.Facebook }), services => { - services.AddCookieAuthentication("External", o => { }); - services.AddFacebookAuthentication(o => + services.AddAuthentication() + .AddCookie("External", o => { }) + .AddFacebook(o => { o.AppId = "Test App Id"; o.AppSecret = "Test App Secret"; - o.SignInScheme = "External"; }); }, handler: null); @@ -155,8 +155,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook }, services => { - services.AddCookieAuthentication("External", o => { }); - services.AddFacebookAuthentication(o => + services.AddAuthentication() + .AddCookie("External", o => { }) + .AddFacebook(o => { o.AppId = "Test App Id"; o.AppSecret = "Test App Secret"; @@ -185,9 +186,9 @@ namespace Microsoft.AspNetCore.Authentication.Facebook services.AddAuthentication(options => { options.DefaultSignInScheme = "External"; - }); - services.AddCookieAuthentication(); - services.AddFacebookAuthentication(o => + }) + .AddCookie() + .AddFacebook(o => { o.AppId = "Test App Id"; o.AppSecret = "Test App Secret"; @@ -217,19 +218,15 @@ namespace Microsoft.AspNetCore.Authentication.Facebook var finalUserInfoEndpoint = string.Empty; var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("FacebookTest")); var server = CreateServer( - app => - { - app.UseAuthentication(); - }, + app => app.UseAuthentication(), services => { services.AddAuthentication(options => { - options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); - services.AddCookieAuthentication(); - services.AddFacebookAuthentication(o => + }) + .AddCookie() + .AddFacebook(o => { o.AppId = "Test App Id"; o.AppSecret = "Test App Secret"; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs index 501fdf6035..2eae266702 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs @@ -516,7 +516,7 @@ namespace Microsoft.AspNetCore.Authentication.Google OnCreatingTicket = context => { var refreshToken = context.RefreshToken; - context.Ticket.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google")); + context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google")); return Task.FromResult(0); } }; @@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Authentication.Google { OnTicketReceived = context => { - context.Ticket.Properties.RedirectUri = null; + context.Properties.RedirectUri = null; return Task.FromResult(0); } }; @@ -985,7 +985,7 @@ namespace Microsoft.AspNetCore.Authentication.Google else if (req.Path == new PathString("/tokens")) { var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme); - var tokens = result.Ticket.Properties.GetTokens(); + var tokens = result.Properties.GetTokens(); res.Describe(tokens); } else if (req.Path == new PathString("/me")) @@ -995,17 +995,17 @@ namespace Microsoft.AspNetCore.Authentication.Google else if (req.Path == new PathString("/authenticate")) { var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme); - res.Describe(result.Ticket.Principal); + res.Describe(result.Principal); } else if (req.Path == new PathString("/authenticateGoogle")) { var result = await context.AuthenticateAsync("Google"); - res.Describe(result?.Ticket?.Principal); + res.Describe(result?.Principal); } else if (req.Path == new PathString("/authenticateFacebook")) { var result = await context.AuthenticateAsync("Facebook"); - res.Describe(result?.Ticket?.Principal); + res.Describe(result?.Principal); } else if (req.Path == new PathString("/unauthorized")) { @@ -1024,15 +1024,15 @@ namespace Microsoft.AspNetCore.Authentication.Google } else if (req.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync("Google", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Google", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Google")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Google")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Google")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Google")); } else if (testpath != null) { @@ -1050,12 +1050,12 @@ namespace Microsoft.AspNetCore.Authentication.Google services.AddAuthentication(o => { o.DefaultAuthenticateScheme = TestExtensions.CookieAuthenticationScheme; - o.DefaultSignInScheme = TestExtensions.CookieAuthenticationScheme; o.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; }); - services.AddCookieAuthentication(TestExtensions.CookieAuthenticationScheme); - services.AddGoogleAuthentication(configureOptions); - services.AddFacebookAuthentication(o => + services.AddAuthentication() + .AddCookie(TestExtensions.CookieAuthenticationScheme) + .AddGoogle(configureOptions) + .AddFacebook(o => { o.AppId = "Test AppId"; o.AppSecret = "Test AppSecrent"; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs index 3149e9875b..a9b158d2b0 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs @@ -127,11 +127,8 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") }; - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)), - new AuthenticationProperties(), context.Scheme.Name); - - context.HandleResponse(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + context.Success(); return Task.FromResult(null); } @@ -338,7 +335,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { // Retrieve the NameIdentifier claim from the identity // returned by the custom security token validator. - var identity = (ClaimsIdentity)context.Ticket.Principal.Identity; + var identity = (ClaimsIdentity)context.Principal.Identity; var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); Assert.Equal("Bob le Tout Puissant", identifier.Value); @@ -396,7 +393,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { OnMessageReceived = context => { - context.Skip(); + context.NoResult(); return Task.FromResult(0); }, OnTokenValidated = context => @@ -420,7 +417,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } [Fact] - public async Task EventOnMessageReceivedHandled_NoMoreEventsExecuted() + public async Task EventOnMessageReceivedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { @@ -428,7 +425,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { OnMessageReceived = context => { - context.HandleResponse(); + context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, @@ -447,9 +444,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }; }); - var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); - Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); - Assert.Equal(string.Empty, response.ResponseText); + var exception = await Assert.ThrowsAsync(delegate + { + return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] @@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { OnTokenValidated = context => { - context.Skip(); + context.NoResult(); return Task.FromResult(0); }, OnAuthenticationFailed = context => @@ -483,7 +483,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } [Fact] - public async Task EventOnTokenValidatedHandled_NoMoreEventsExecuted() + public async Task EventOnTokenValidatedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { @@ -491,7 +491,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { OnTokenValidated = context => { - context.HandleResponse(); + context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, @@ -508,9 +508,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); - var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); - Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); - Assert.Equal(string.Empty, response.ResponseText); + var exception = await Assert.ThrowsAsync(delegate + { + return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] @@ -526,7 +529,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }, OnAuthenticationFailed = context => { - context.Skip(); + context.NoResult(); return Task.FromResult(0); }, OnChallenge = context => @@ -544,7 +547,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } [Fact] - public async Task EventOnAuthenticationFailedHandled_NoMoreEventsExecuted() + public async Task EventOnAuthenticationFailedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { @@ -556,7 +559,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }, OnAuthenticationFailed = context => { - context.HandleResponse(); + context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, @@ -569,9 +572,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); - var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); - Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); - Assert.Equal(string.Empty, response.ResponseText); + var exception = await Assert.ThrowsAsync(delegate + { + return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] @@ -583,7 +589,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { OnChallenge = context => { - context.Skip(); + context.HandleResponse(); return Task.FromResult(0); }, }; @@ -595,29 +601,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } - - [Fact] - public async Task EventOnChallengeHandled_ResponseNotModified() - { - var server = CreateServer(o => - { - o.Events = new JwtBearerEvents() - { - OnChallenge = context => - { - context.HandleResponse(); - context.Response.StatusCode = StatusCodes.Status202Accepted; - return Task.FromResult(0); - }, - }; - }); - - var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); - Assert.Equal(HttpStatusCode.Accepted, response.Response.StatusCode); - Assert.Empty(response.Response.Headers.WwwAuthenticate); - Assert.Equal(string.Empty, response.ResponseText); - } - class InvalidTokenValidator : ISecurityTokenValidator { public InvalidTokenValidator() @@ -752,11 +735,11 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } else if (context.Request.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); } else if (context.Request.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync(JwtBearerDefaults.AuthenticationScheme)); + await Assert.ThrowsAsync(() => context.SignOutAsync(JwtBearerDefaults.AuthenticationScheme)); } else { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs index bf15c91b00..f516940432 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount OnCreatingTicket = context => { var refreshToken = context.RefreshToken; - context.Ticket.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Microsoft") }, "Microsoft")); + context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Microsoft") }, "Microsoft")); return Task.FromResult(null); } }; @@ -205,15 +205,15 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount } else if (req.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync("Microsoft", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Microsoft", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Microsoft")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Microsoft")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Microsoft")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Microsoft")); } else { @@ -226,10 +226,10 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount services.AddAuthentication(o => { o.DefaultAuthenticateScheme = TestExtensions.CookieAuthenticationScheme; - o.DefaultSignInScheme = TestExtensions.CookieAuthenticationScheme; }); - services.AddCookieAuthentication(TestExtensions.CookieAuthenticationScheme, o => { }); - services.AddMicrosoftAccountAuthentication(configureOptions); + services.AddAuthentication() + .AddCookie(TestExtensions.CookieAuthenticationScheme, o => { }) + .AddMicrosoftAccount(configureOptions); }); return new TestServer(builder); } diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs index dcc96c0942..bdd79533b1 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs @@ -143,29 +143,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); } - [Fact] - public async Task ThrowsIfSignInSchemeMissing() - { - var server = CreateServer( - app => { }, - services => services.AddOAuthAuthentication("weeblie", o => - { - o.ClientId = "Whatever;"; - o.ClientSecret = "Whatever;"; - o.CallbackPath = "/"; - o.TokenEndpoint = "/"; - o.AuthorizationEndpoint = "/"; - }), - context => - { - // REVIEW: Gross. - Assert.Throws("SignInScheme", () => context.ChallengeAsync("weeblie").GetAwaiter().GetResult()); - return true; - }); - var transaction = await server.SendAsync("http://example.com/challenge"); - Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); - } - [Fact] public async Task RedirectToIdentityProvider_SetsCorrelationIdCookiePath_ToCallBackPath() { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectChallengeTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectChallengeTests.cs index 3c0146b083..d21a1f4246 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectChallengeTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectChallengeTests.cs @@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect // This test can be further refined. When one auth handler skips, the authentication responsibility // will be flowed to the next one. A dummy auth handler can be added to ensure the correct logic. [Fact] - public async Task OnRedirectToIdentityProviderEventSkipResponse() + public async Task OnRedirectToIdentityProviderEventHandleResponse() { var settings = new TestSettings( opts => @@ -346,7 +346,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { OnRedirectToIdentityProvider = context => { - context.Skip(); + context.HandleResponse(); return Task.FromResult(0); } }; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectConfigurationTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectConfigurationTests.cs index 3ceb4a5336..74f00c8f95 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectConfigurationTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectConfigurationTests.cs @@ -22,8 +22,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect var builder = new WebHostBuilder() .ConfigureServices(services => { - services.AddCookieAuthentication(); - services.AddOpenIdConnectAuthentication(o => + services.AddAuthentication() + .AddCookie() + .AddOpenIdConnect(o => { o.Authority = TestServerBuilder.DefaultAuthority; o.ClientId = Guid.NewGuid().ToString(); @@ -45,19 +46,6 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); } - [Fact] - public Task ThrowsWhenSignInSchemeIsMissing() - { - return TestConfigurationException( - o => - { - o.ClientId = "Test Id"; - o.Authority = TestServerBuilder.DefaultAuthority; - o.CallbackPath = "/"; - }, - ex => Assert.Equal("SignInScheme", ex.ParamName)); - } - [Fact] public Task ThrowsWhenClientIdIsMissing() { @@ -119,8 +107,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect var builder = new WebHostBuilder() .ConfigureServices(services => { - services.AddCookieAuthentication(); - services.AddOpenIdConnectAuthentication(options); + services.AddAuthentication() + .AddCookie() + .AddOpenIdConnect(options); }) .Configure(app => app.UseAuthentication()); diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs index 607e9bb623..4ea69369e8 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect private readonly Func UserNotImpl = context => { throw new NotImplementedException("User"); }; private readonly Func FailedNotImpl = context => { throw new NotImplementedException("Failed", context.Exception); }; private readonly Func TicketNotImpl = context => { throw new NotImplementedException("Ticket"); }; - private readonly Func FailureNotImpl = context => { throw new NotImplementedException("Failure", context.Failure); }; + private readonly Func FailureNotImpl = context => { throw new NotImplementedException("Failure", context.Failure); }; private readonly Func RedirectNotImpl = context => { throw new NotImplementedException("Redirect"); }; private readonly Func RemoteSignOutNotImpl = context => { throw new NotImplementedException("Remote"); }; private readonly RequestDelegate AppNotImpl = context => { throw new NotImplementedException("App"); }; @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnMessageReceived = context => { messageReceived = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnTokenValidated = TokenNotImpl, @@ -75,6 +75,51 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(messageReceived); } + [Fact] + public async Task OnMessageReceived_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnTokenValidated = TokenNotImpl, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", ""); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(remoteFailure); + } + [Fact] public async Task OnMessageReceived_Handled_NoMoreEventsRun() { @@ -124,7 +169,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenValidated = context => { tokenValidated = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnAuthorizationCodeReceived = CodeNotImpl, @@ -151,6 +196,57 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(tokenValidated); } + [Fact] + public async Task OnTokenValidated_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = CodeNotImpl, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(remoteFailure); + } + [Fact] public async Task OnTokenValidated_HandledWithoutTicket_NoMoreEventsRun() { @@ -167,7 +263,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { tokenValidated = true; context.HandleResponse(); - context.Ticket = null; + context.Principal = null; context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, @@ -209,8 +305,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenValidated = context => { tokenValidated = true; - context.HandleResponse(); - // context.Ticket = null; + context.Success(); return Task.FromResult(0); }, OnAuthorizationCodeReceived = CodeNotImpl, @@ -262,7 +357,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnAuthorizationCodeReceived = context => { codeReceived = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnTokenResponseReceived = TokenResponseNotImpl, @@ -289,6 +384,63 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(codeReceived); } + [Fact] + public async Task OnAuthorizationCodeReceived_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = TokenResponseNotImpl, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(remoteFailure); + } + [Fact] public async Task OnAuthorizationCodeReceived_HandledWithoutTicket_NoMoreEventsRun() { @@ -311,7 +463,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { codeReceived = true; context.HandleResponse(); - context.Ticket = null; + context.Principal = null; context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, @@ -358,8 +510,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnAuthorizationCodeReceived = context => { codeReceived = true; - context.HandleResponse(); - // context.Ticket = null; + context.Success(); return Task.FromResult(0); }, OnTokenResponseReceived = TokenResponseNotImpl, @@ -417,7 +568,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenResponseReceived = context => { tokenResponseReceived = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnUserInformationReceived = UserNotImpl, @@ -444,6 +595,69 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(tokenResponseReceived); } + [Fact] + public async Task OnTokenResponseReceived_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(remoteFailure); + } + [Fact] public async Task OnTokenResponseReceived_HandledWithoutTicket_NoMoreEventsRun() { @@ -471,7 +685,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenResponseReceived = context => { tokenResponseReceived = true; - context.Ticket = null; + context.Principal = null; context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); @@ -525,8 +739,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenResponseReceived = context => { tokenResponseReceived = true; - // context.Ticket = null; - context.HandleResponse(); + context.Success(); return Task.FromResult(0); }, OnUserInformationReceived = UserNotImpl, @@ -584,7 +797,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenValidated = context => { tokenValidated = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnUserInformationReceived = UserNotImpl, @@ -611,6 +824,69 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(tokenValidated); } + [Fact] + public async Task OnTokenValidatedBackchannel_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var codeReceived = false; + var tokenResponseReceived = false; + var tokenValidated = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = UserNotImpl, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "state=protected_state&code=my_code"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(tokenValidated); + Assert.True(remoteFailure); + } + [Fact] public async Task OnTokenValidatedBackchannel_HandledWithoutTicket_NoMoreEventsRun() { @@ -638,7 +914,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenValidated = context => { tokenValidated = true; - context.Ticket = null; + context.Principal = null; context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); @@ -692,8 +968,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTokenValidated = context => { tokenValidated = true; - // context.Ticket = null; - context.HandleResponse(); + context.Success(); return Task.FromResult(0); }, OnUserInformationReceived = UserNotImpl, @@ -757,7 +1032,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnUserInformationReceived = context => { userInfoReceived = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnAuthenticationFailed = FailedNotImpl, @@ -784,6 +1059,75 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(userInfoReceived); } + [Fact] + public async Task OnUserInformationReceived_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnAuthenticationFailed = FailedNotImpl, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(remoteFailure); + } + [Fact] public async Task OnUserInformationReceived_HandledWithoutTicket_NoMoreEventsRun() { @@ -817,7 +1161,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnUserInformationReceived = context => { userInfoReceived = true; - context.Ticket = null; + context.Principal = null; context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); @@ -878,7 +1222,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { userInfoReceived = true; // context.Ticket = null; - context.HandleResponse(); + context.Success(); return Task.FromResult(0); }, OnAuthenticationFailed = FailedNotImpl, @@ -949,7 +1293,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { authFailed = true; Assert.Equal("TestException", context.Exception.Message); - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnRemoteFailure = FailureNotImpl, @@ -976,6 +1320,82 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.True(authFailed); } + [Fact] + public async Task OnAuthenticationFailed_Reject_NoMoreEventsRun() + { + var messageReceived = false; + var tokenValidated = false; + var codeReceived = false; + var tokenResponseReceived = false; + var userInfoReceived = false; + var authFailed = false; + var remoteFailure = false; + var server = CreateServer(new OpenIdConnectEvents() + { + OnMessageReceived = context => + { + messageReceived = true; + return Task.FromResult(0); + }, + OnTokenValidated = context => + { + tokenValidated = true; + return Task.FromResult(0); + }, + OnAuthorizationCodeReceived = context => + { + codeReceived = true; + return Task.FromResult(0); + }, + OnTokenResponseReceived = context => + { + tokenResponseReceived = true; + return Task.FromResult(0); + }, + OnUserInformationReceived = context => + { + userInfoReceived = true; + throw new NotImplementedException("TestException"); + }, + OnAuthenticationFailed = context => + { + authFailed = true; + Assert.Equal("TestException", context.Exception.Message); + context.Fail("Authentication was aborted from user code."); + return Task.FromResult(0); + }, + OnRemoteFailure = context => + { + remoteFailure = true; + return Task.FromResult(0); + }, + OnTicketReceived = TicketNotImpl, + + OnRedirectToIdentityProvider = RedirectNotImpl, + OnRedirectToIdentityProviderForSignOut = RedirectNotImpl, + OnRemoteSignOut = RemoteSignOutNotImpl, + }, + context => + { + return context.Response.WriteAsync(context.Request.Path); + }); + + var exception = await Assert.ThrowsAsync(delegate + { + return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code"); + }); + + Assert.Equal("Authentication was aborted from user code.", exception.Message); + + Assert.True(messageReceived); + Assert.True(tokenValidated); + Assert.True(codeReceived); + Assert.True(tokenResponseReceived); + Assert.True(userInfoReceived); + Assert.True(authFailed); + Assert.True(remoteFailure); + } + [Fact] public async Task OnAuthenticationFailed_HandledWithoutTicket_NoMoreEventsRun() { @@ -1016,7 +1436,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { authFailed = true; Assert.Equal("TestException", context.Exception.Message); - Assert.Null(context.Ticket); + Assert.Null(context.Principal); context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); @@ -1083,7 +1503,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { authFailed = true; Assert.Equal("TestException", context.Exception.Message); - Assert.Null(context.Ticket); + Assert.Null(context.Principal); var claims = new[] { @@ -1092,11 +1512,8 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") }; - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)), - new AuthenticationProperties(), context.Scheme.Name); - - context.HandleResponse(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + context.Success(); return Task.FromResult(0); }, OnRemoteFailure = FailureNotImpl, @@ -1174,7 +1591,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect { remoteFailure = true; Assert.Equal("TestException", context.Failure.Message); - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, OnTicketReceived = TicketNotImpl, @@ -1314,7 +1731,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect OnTicketReceived = context => { ticektReceived = true; - context.Skip(); + context.SkipHandler(); return Task.FromResult(0); }, @@ -1408,8 +1825,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect var builder = new WebHostBuilder() .ConfigureServices(services => { - services.AddCookieAuthentication(); - services.AddOpenIdConnectAuthentication(o => + services.AddAuthentication() + .AddCookie() + .AddOpenIdConnect(o => { o.Events = events; o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs index 6cfda3b85a..7f9d42b1c9 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -11,10 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Xunit; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/TestServerBuilder.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/TestServerBuilder.cs index aa7f6179be..1ffb9ff686 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/TestServerBuilder.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/TestServerBuilder.cs @@ -112,10 +112,10 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect services.AddAuthentication(o => { o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }); - services.AddCookieAuthentication(); - services.AddOpenIdConnectAuthentication(options); + services.AddAuthentication() + .AddCookie() + .AddOpenIdConnect(options); }); return new TestServer(builder); diff --git a/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs index afdef0a408..05e5708de0 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Authentication // return Task.FromResult(0); // } - // public Task ChallengeAsync(ChallengeContext context) + // public Task ChallengeAsync(AuthenticationProperties properties) // { // throw new NotImplementedException(); // } @@ -144,12 +144,12 @@ namespace Microsoft.AspNetCore.Authentication // throw new NotImplementedException(); // } - // public Task SignInAsync(SignInContext context) + // public Task SignInAsync(ClaimsPrincipal principal, AuthenticationProperties properties) // { // throw new NotImplementedException(); // } - // public Task SignOutAsync(SignOutContext context) + // public Task SignOutAsync(AuthenticationProperties properties) // { // throw new NotImplementedException(); // } diff --git a/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs index 8fcc0780d2..76f6b1aad9 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Security.Claims; @@ -13,7 +12,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Authentication.Twitter @@ -177,15 +175,15 @@ namespace Microsoft.AspNetCore.Authentication.Twitter var res = context.Response; if (req.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync("Twitter", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Twitter", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Twitter")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Twitter")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Twitter")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Twitter")); } else if (handler == null || !handler(context)) { @@ -195,13 +193,14 @@ namespace Microsoft.AspNetCore.Authentication.Twitter }) .ConfigureServices(services => { - services.AddCookieAuthentication("External", _ => { }); Action wrapOptions = o => { o.SignInScheme = "External"; options(o); }; - services.AddTwitterAuthentication(wrapOptions); + services.AddAuthentication() + .AddCookie("External", _ => { }) + .AddTwitter(wrapOptions); }); return new TestServer(builder); } diff --git a/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs b/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs index b6b2776a8a..5c2458c529 100644 --- a/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs +++ b/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs @@ -312,7 +312,7 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test var builder = new WebHostBuilder() .ConfigureServices(services => { - services.AddCookieAuthentication(o => + services.AddAuthentication().AddCookie(o => { o.CookieName = "TestCookie"; o.CookieHttpOnly = false; @@ -352,7 +352,7 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test var builder = new WebHostBuilder() .ConfigureServices(services => { - services.AddCookieAuthentication(o => + services.AddAuthentication().AddCookie(o => { o.CookieName = "TestCookie"; o.CookieHttpOnly = false; diff --git a/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs b/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs index ae5e6f0183..e2e4fd7d07 100644 --- a/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs +++ b/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs @@ -68,7 +68,7 @@ namespace Microsoft.Owin.Security.Interop await context.Response.WriteAsync(result.Ticket.Principal.Identity.Name); }); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.DataProtectionProvider = dataProtection)); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.DataProtectionProvider = dataProtection)); var newServer = new AspNetCore.TestHost.TestServer(builder); var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/login"); @@ -123,7 +123,7 @@ namespace Microsoft.Owin.Security.Interop await context.Response.WriteAsync(result.Ticket.Principal.Identity.Name); }); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.DataProtectionProvider = dataProtection)); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.DataProtectionProvider = dataProtection)); var newServer = new AspNetCore.TestHost.TestServer(builder); var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/login"); @@ -155,7 +155,7 @@ namespace Microsoft.Owin.Security.Interop app.UseAuthentication(); app.Run(context => context.SignInAsync("Cookies", user)); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.DataProtectionProvider = dataProtection)); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.DataProtectionProvider = dataProtection)); var newServer = new AspNetCore.TestHost.TestServer(builder); var cookies = await SendAndGetCookies(newServer, "http://example.com/login"); @@ -202,7 +202,7 @@ namespace Microsoft.Owin.Security.Interop app.UseAuthentication(); app.Run(context => context.SignInAsync("Cookies", user)); }) - .ConfigureServices(services => services.AddCookieAuthentication(o => o.DataProtectionProvider = dataProtection)); + .ConfigureServices(services => services.AddAuthentication().AddCookie(o => o.DataProtectionProvider = dataProtection)); var newServer = new AspNetCore.TestHost.TestServer(builder); var cookies = await SendAndGetCookies(newServer, "http://example.com/login");