diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAppBuilderExtensions.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAppBuilderExtensions.cs index d865dc4ce6..e125ecc162 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAppBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAppBuilderExtensions.cs @@ -28,5 +28,19 @@ namespace Microsoft.AspNet.Builder Name = optionsName }); } + + /// + /// Adds a cookie-based authentication middleware to your web application pipeline. + /// + /// The IApplicationBuilder passed to your configuration method + /// Used to configure the options for the middleware + /// The name of the options class that controls the middleware behavior, null will use the default options + /// The original app parameter + public static IApplicationBuilder UseCookieAuthentication([NotNull] this IApplicationBuilder app, IOptions options) + { + return app.UseMiddleware(options, + new ConfigureOptions(o => { })); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationDefaults.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationDefaults.cs index 95981a0f0a..d47cca2052 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationDefaults.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationDefaults.cs @@ -22,19 +22,26 @@ namespace Microsoft.AspNet.Authentication.Cookies public const string CookiePrefix = ".AspNet."; /// - /// The default value used by UseApplicationSignInCookie for the + /// The default value used by CookieAuthenticationMiddleware for the /// CookieAuthenticationOptions.LoginPath /// [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")] public static readonly PathString LoginPath = new PathString("/Account/Login"); /// - /// The default value used by UseApplicationSignInCookie for the + /// The default value used by CookieAuthenticationMiddleware for the /// CookieAuthenticationOptions.LogoutPath /// [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")] public static readonly PathString LogoutPath = new PathString("/Account/Logout"); + /// + /// The default value used by CookieAuthenticationMiddleware for the + /// CookieAuthenticationOptions.AccessDeniedPath + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")] + public static readonly PathString AccessDeniedPath = new PathString("/Account/AccessDenied"); + /// /// The default value of the CookieAuthenticationOptions.ReturnUrlParameter /// diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs index 12c40ea5cd..8919d76809 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -376,12 +376,6 @@ namespace Microsoft.AspNet.Authentication.Cookies protected override Task HandleForbiddenAsync(ChallengeContext context) { - // HandleForbidden by redirecting to AccessDeniedPath if set - if (!Options.AccessDeniedPath.HasValue) - { - return base.HandleForbiddenAsync(context); - } - try { var accessDeniedUri = @@ -409,11 +403,6 @@ namespace Microsoft.AspNet.Authentication.Cookies protected override Task HandleUnauthorizedAsync([NotNull] ChallengeContext context) { - if (!Options.LoginPath.HasValue) - { - return base.HandleUnauthorizedAsync(context); - } - var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri; try { diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs index 690e6d50d7..a0f98da50a 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs @@ -40,6 +40,18 @@ namespace Microsoft.AspNet.Authentication.Cookies { Options.CookieManager = new ChunkingCookieManager(urlEncoder); } + if (!Options.LoginPath.HasValue) + { + Options.LoginPath = CookieAuthenticationDefaults.LoginPath; + } + if (!Options.LogoutPath.HasValue) + { + Options.LogoutPath = CookieAuthenticationDefaults.LogoutPath; + } + if (!Options.AccessDeniedPath.HasValue) + { + Options.AccessDeniedPath = CookieAuthenticationDefaults.AccessDeniedPath; + } } protected override AuthenticationHandler CreateHandler() diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs index 0d5e5c16bb..468d07522b 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs @@ -88,9 +88,6 @@ namespace Microsoft.AspNet.Authentication.Cookies /// to the LoginPath as a query string parameter named by the ReturnUrlParameter. Once a request to the /// LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect the browser back /// to the url which caused the original unauthorized status code. - /// - /// If the LoginPath is null or empty, the middleware will not look for 401 Unauthorized status codes, and it will - /// not redirect automatically when a login occurs. /// [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")] public PathString LoginPath { get; set; } @@ -104,9 +101,6 @@ namespace Microsoft.AspNet.Authentication.Cookies /// /// The AccessDeniedPath property informs the middleware that it should change an outgoing 403 Forbidden status /// code into a 302 redirection onto the given path. - /// - /// If the AccessDeniedPath is null or empty, the middleware will not look for 403 Forbidden status codes, and it will - /// not redirect /// public PathString AccessDeniedPath { get; set; } diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs index d327f9dca5..3a5da62485 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs @@ -38,11 +38,11 @@ namespace Microsoft.AspNet.Authentication.Facebook ConfigureOptions configureOptions = null) : base(next, dataProtectionProvider, loggerFactory, encoder, sharedOptions, options, configureOptions) { - if (string.IsNullOrWhiteSpace(Options.AppId)) + if (string.IsNullOrEmpty(Options.AppId)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppId))); } - if (string.IsNullOrWhiteSpace(Options.AppSecret)) + if (string.IsNullOrEmpty(Options.AppSecret)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppSecret))); } diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs index 0fac563eb8..1c03b51183 100644 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount } var email = MicrosoftAccountAuthenticationHelper.GetEmail(payload); - if (!string.IsNullOrWhiteSpace(email)) + if (!string.IsNullOrEmpty(email)) { identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer)); } diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs index f99f99d666..818ba20fba 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs @@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Authentication.OAuth var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath)); - if (string.IsNullOrWhiteSpace(tokens.AccessToken)) + if (string.IsNullOrEmpty(tokens.AccessToken)) { Logger.LogWarning("Access token was not found"); return new AuthenticationTicket(properties, Options.AuthenticationScheme); @@ -279,7 +279,7 @@ namespace Microsoft.AspNet.Authentication.OAuth { var correlationKey = Constants.CorrelationPrefix + Options.AuthenticationScheme; var correlationCookie = Request.Cookies[correlationKey]; - if (string.IsNullOrWhiteSpace(correlationCookie)) + if (string.IsNullOrEmpty(correlationCookie)) { Logger.LogWarning("{0} cookie not found.", correlationKey); return false; @@ -312,4 +312,4 @@ namespace Microsoft.AspNet.Authentication.OAuth return true; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs index 5ac0521f3d..10ae285e96 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs @@ -38,27 +38,27 @@ namespace Microsoft.AspNet.Authentication.OAuth : base(next, options, loggerFactory, encoder, configureOptions) { // todo: review error handling - if (string.IsNullOrWhiteSpace(Options.AuthenticationScheme)) + if (string.IsNullOrEmpty(Options.AuthenticationScheme)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AuthenticationScheme))); } - if (string.IsNullOrWhiteSpace(Options.ClientId)) + if (string.IsNullOrEmpty(Options.ClientId)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ClientId))); } - if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + if (string.IsNullOrEmpty(Options.ClientSecret)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ClientSecret))); } - if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint)) + if (string.IsNullOrEmpty(Options.AuthorizationEndpoint)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AuthorizationEndpoint))); } - if (string.IsNullOrWhiteSpace(Options.TokenEndpoint)) + if (string.IsNullOrEmpty(Options.TokenEndpoint)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.TokenEndpoint))); } diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs index c3e9499c12..ef04f14a1f 100644 --- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer var validationParameters = Options.TokenValidationParameters.Clone(); if (_configuration != null) { - if (validationParameters.ValidIssuer == null && !string.IsNullOrWhiteSpace(_configuration.Issuer)) + if (validationParameters.ValidIssuer == null && !string.IsNullOrEmpty(_configuration.Issuer)) { validationParameters.ValidIssuer = _configuration.Issuer; } diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs index 67ddf70702..9fcd66c5d8 100644 --- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer Options.SecurityTokenValidators = new List { new JwtSecurityTokenHandler() }; } - if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.Audience)) + if (string.IsNullOrEmpty(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(Options.Audience)) { Options.TokenValidationParameters.ValidAudience = Options.Audience; } @@ -59,9 +59,9 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer { Options.ConfigurationManager = new StaticConfigurationManager(Options.Configuration); } - else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority))) + else if (!(string.IsNullOrEmpty(Options.MetadataAddress) && string.IsNullOrEmpty(Options.Authority))) { - if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority)) + if (string.IsNullOrEmpty(Options.MetadataAddress) && !string.IsNullOrEmpty(Options.Authority)) { Options.MetadataAddress = Options.Authority; if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal)) diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs index 3e25fe80e1..4525c6578c 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs @@ -251,7 +251,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect // response_mode=query (explicit or not) and a response_type containing id_token // or token are not considered as a safe combination and MUST be rejected. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security - if (!string.IsNullOrWhiteSpace(message.IdToken) || !string.IsNullOrWhiteSpace(message.Token)) + if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.Token)) { Logger.LogError("An OpenID Connect response cannot contain an identity token " + "or an access token when using response_mode=query"); @@ -260,7 +260,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect } // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrWhiteSpace(Request.ContentType) + && !string.IsNullOrEmpty(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) && Request.Body.CanRead) @@ -643,7 +643,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect { // assume a well formed query string: OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d> var startIndex = 0; - if (string.IsNullOrWhiteSpace(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1) + if (string.IsNullOrEmpty(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1) { return null; } @@ -934,7 +934,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect } // Redirect back to the original secured resource, if any. - if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri)) + if (!string.IsNullOrEmpty(ticket.Properties.RedirectUri)) { Response.Redirect(ticket.Properties.RedirectUri); return true; diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs index 1f25035533..f08577d0e9 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs @@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect Options.Notifications = new OpenIdConnectAuthenticationNotifications(); } - if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.ClientId)) + if (string.IsNullOrEmpty(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(Options.ClientId)) { Options.TokenValidationParameters.ValidAudience = Options.ClientId; } @@ -116,9 +116,9 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect { Options.ConfigurationManager = new StaticConfigurationManager(Options.Configuration); } - else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority))) + else if (!(string.IsNullOrEmpty(Options.MetadataAddress) && string.IsNullOrEmpty(Options.Authority))) { - if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority)) + if (string.IsNullOrEmpty(Options.MetadataAddress) && !string.IsNullOrEmpty(Options.Authority)) { Options.MetadataAddress = Options.Authority; if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal)) diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs index d6fea526dc..fb8f3aa254 100644 --- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Authentication.Twitter properties = requestToken.Properties; var returnedToken = query.Get("oauth_token"); - if (string.IsNullOrWhiteSpace(returnedToken)) + if (string.IsNullOrEmpty(returnedToken)) { Logger.LogWarning("Missing oauth_token"); return new AuthenticationTicket(properties, Options.AuthenticationScheme); @@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Authentication.Twitter } var oauthVerifier = query.Get("oauth_verifier"); - if (string.IsNullOrWhiteSpace(oauthVerifier)) + if (string.IsNullOrEmpty(oauthVerifier)) { Logger.LogWarning("Missing or blank oauth_verifier"); return new AuthenticationTicket(properties, Options.AuthenticationScheme); diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs index cc5faba732..30d4fe3e98 100644 --- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs @@ -42,11 +42,11 @@ namespace Microsoft.AspNet.Authentication.Twitter ConfigureOptions configureOptions = null) : base(next, options, loggerFactory, encoder, configureOptions) { - if (string.IsNullOrWhiteSpace(Options.ConsumerSecret)) + if (string.IsNullOrEmpty(Options.ConsumerSecret)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerSecret))); } - if (string.IsNullOrWhiteSpace(Options.ConsumerKey)) + if (string.IsNullOrEmpty(Options.ConsumerKey)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerKey))); } diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs index 623ac82f9c..d576f1014c 100644 --- a/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs @@ -14,11 +14,11 @@ namespace Microsoft.AspNet.Authentication /// /// Base class for the per-request work performed by most authentication middleware. /// - public abstract class AuthenticationHandler : IAuthenticationHandler + /// Specifies which type for of AuthenticationOptions property + public abstract class AuthenticationHandler : IAuthenticationHandler where TOptions : AuthenticationOptions { private Task _authenticateTask; private bool _finishCalled; - private AuthenticationOptions _baseOptions; protected bool SignInAccepted { get; set; } protected bool SignOutAccepted { get; set; } @@ -44,11 +44,6 @@ namespace Microsoft.AspNet.Authentication protected IUrlEncoder UrlEncoder { get; private set; } - internal AuthenticationOptions BaseOptions - { - get { return _baseOptions; } - } - public IAuthenticationHandler PriorHandler { get; set; } protected string CurrentUri @@ -59,9 +54,18 @@ namespace Microsoft.AspNet.Authentication } } - protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder) + protected TOptions Options { get; private set; } + + /// + /// Initialize is called once per request to contextualize this instance with appropriate state. + /// + /// The original options passed by the application control behavior + /// The utility object to observe the current request and response + /// The logging factory used to create loggers + /// async completion + public async Task InitializeAsync([NotNull] TOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder) { - _baseOptions = options; + Options = options; Context = context; OriginalPathBase = Request.PathBase; OriginalPath = Request.Path; @@ -90,7 +94,7 @@ namespace Microsoft.AspNet.Authentication private static async Task OnStartingCallback(object state) { - var handler = (AuthenticationHandler)state; + var handler = (AuthenticationHandler)state; await handler.FinishResponseOnce(); } @@ -115,9 +119,9 @@ namespace Microsoft.AspNet.Authentication private async Task HandleAutomaticChallengeIfNeeded() { - if (!ChallengeCalled && BaseOptions.AutomaticAuthentication && Response.StatusCode == 401) + if (!ChallengeCalled && Options.AutomaticAuthentication && Response.StatusCode == 401) { - await HandleUnauthorizedAsync(new ChallengeContext(BaseOptions.AuthenticationScheme)); + await HandleUnauthorizedAsync(new ChallengeContext(Options.AuthenticationScheme)); } } @@ -152,7 +156,7 @@ namespace Microsoft.AspNet.Authentication public void GetDescriptions(DescribeSchemesContext describeContext) { - describeContext.Accept(BaseOptions.Description.Items); + describeContext.Accept(Options.Description.Items); if (PriorHandler != null) { @@ -162,8 +166,8 @@ namespace Microsoft.AspNet.Authentication public bool ShouldHandleScheme(string authenticationScheme) { - return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) || - (BaseOptions.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme)); + return string.Equals(Options.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) || + (Options.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme)); } public async Task AuthenticateAsync(AuthenticateContext context) @@ -174,7 +178,7 @@ namespace Microsoft.AspNet.Authentication var ticket = await HandleAuthenticateOnceAsync(); if (ticket?.Principal != null) { - context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items); + context.Authenticated(ticket.Principal, ticket.Properties.Items, Options.Description.Items); } else { diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationHandler`1.cs b/src/Microsoft.AspNet.Authentication/AuthenticationHandler`1.cs deleted file mode 100644 index edc127e266..0000000000 --- a/src/Microsoft.AspNet.Authentication/AuthenticationHandler`1.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.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.Framework.Logging; -using Microsoft.Framework.WebEncoders; - -namespace Microsoft.AspNet.Authentication -{ - /// - /// Base class for the per-request work performed by most authentication middleware. - /// - /// Specifies which type for of AuthenticationOptions property - public abstract class AuthenticationHandler : AuthenticationHandler where TOptions : AuthenticationOptions - { - protected TOptions Options { get; private set; } - - /// - /// Initialize is called once per request to contextualize this instance with appropriate state. - /// - /// The original options passed by the application control behavior - /// The utility object to observe the current request and response - /// The logging factory used to create loggers - /// async completion - public Task Initialize(TOptions options, HttpContext context, ILogger logger, IUrlEncoder encoder) - { - Options = options; - return BaseInitializeAsync(options, context, logger, encoder); - } - } -} diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs index b8b57d7472..7d83092a40 100644 --- a/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication public async Task Invoke(HttpContext context) { var handler = CreateHandler(); - await handler.Initialize(Options, context, Logger, UrlEncoder); + await handler.InitializeAsync(Options, context, Logger, UrlEncoder); try { if (!await handler.InvokeAsync()) diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs index a2b198ca33..a2ccd6c976 100644 --- a/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs @@ -18,6 +18,12 @@ namespace Microsoft.Framework.DependencyInjection return services; } + public static IServiceCollection AddAuthentication([NotNull] this IServiceCollection services, [NotNull] Action configureOptions) + { + services.Configure(configureOptions); + return services.AddAuthentication(); + } + public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action configure) { return services.Configure(configure); diff --git a/src/Microsoft.AspNet.Authentication/CertificateSubjectKeyIdentifierValidator.cs b/src/Microsoft.AspNet.Authentication/CertificateSubjectKeyIdentifierValidator.cs index ce13de2510..4bb78f8d9b 100644 --- a/src/Microsoft.AspNet.Authentication/CertificateSubjectKeyIdentifierValidator.cs +++ b/src/Microsoft.AspNet.Authentication/CertificateSubjectKeyIdentifierValidator.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication foreach (var chainElement in chain.ChainElements) { string subjectKeyIdentifier = GetSubjectKeyIdentifier(chainElement.Certificate); - if (string.IsNullOrWhiteSpace(subjectKeyIdentifier)) + if (string.IsNullOrEmpty(subjectKeyIdentifier)) { continue; } diff --git a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs index 2a3a9044f1..219b63edac 100644 --- a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs +++ b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs @@ -3,9 +3,11 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Internal; using Microsoft.Framework.Logging; +using Moq; using Xunit; namespace Microsoft.AspNet.Authentication @@ -13,12 +15,12 @@ namespace Microsoft.AspNet.Authentication public class AuthenticationHandlerFacts { [Fact] - public void ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme() + public async Task ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme() { - var handler = new TestHandler("Alpha"); + var handler = await TestHandler.Create("Alpha"); var passiveNoMatch = handler.ShouldHandleScheme("Beta"); - handler = new TestHandler("Alpha"); + handler = await TestHandler.Create("Alpha"); var passiveWithMatch = handler.ShouldHandleScheme("Alpha"); Assert.False(passiveNoMatch); @@ -26,44 +28,44 @@ namespace Microsoft.AspNet.Authentication } [Fact] - public void AutomaticHandlerInAutomaticModeHandlesEmptyChallenges() + public async Task AutomaticHandlerInAutomaticModeHandlesEmptyChallenges() { - var handler = new TestAutoHandler("ignored", true); + var handler = await TestAutoHandler.Create("ignored", true); Assert.True(handler.ShouldHandleScheme("")); } [Fact] - public void AutomaticHandlerHandlesNullScheme() + public async Task AutomaticHandlerHandlesNullScheme() { - var handler = new TestAutoHandler("ignored", true); + var handler = await TestAutoHandler.Create("ignored", true); Assert.True(handler.ShouldHandleScheme(null)); } [Fact] - public void AutomaticHandlerIgnoresWhitespaceScheme() + public async Task AutomaticHandlerIgnoresWhitespaceScheme() { - var handler = new TestAutoHandler("ignored", true); + var handler = await TestAutoHandler.Create("ignored", true); Assert.False(handler.ShouldHandleScheme(" ")); } [Fact] - public void AutomaticHandlerShouldHandleSchemeWhenSchemeMatches() + public async Task AutomaticHandlerShouldHandleSchemeWhenSchemeMatches() { - var handler = new TestAutoHandler("Alpha", true); + var handler = await TestAutoHandler.Create("Alpha", true); Assert.True(handler.ShouldHandleScheme("Alpha")); } [Fact] - public void AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches() + public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches() { - var handler = new TestAutoHandler("Dog", true); + var handler = await TestAutoHandler.Create("Dog", true); Assert.False(handler.ShouldHandleScheme("Alpha")); } [Fact] - public void AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty() + public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty() { - var handler = new TestAutoHandler(null, true); + var handler = await TestAutoHandler.Create(null, true); Assert.False(handler.ShouldHandleScheme("Alpha")); } @@ -72,22 +74,31 @@ namespace Microsoft.AspNet.Authentication [InlineData("")] public async Task AuthHandlerAuthenticateCachesTicket(string scheme) { - var handler = new CountHandler(scheme); + var handler = await CountHandler.Create(scheme); var context = new AuthenticateContext(scheme); await handler.AuthenticateAsync(context); await handler.AuthenticateAsync(context); Assert.Equal(1, handler.AuthCount); } - private class CountHandler : AuthenticationHandler + private class CountOptions : AuthenticationOptions { } + + private class CountHandler : AuthenticationHandler { public int AuthCount = 0; - public CountHandler(string scheme) + private CountHandler() { } + + public static async Task Create(string scheme) { - Initialize(new TestOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default); - Options.AuthenticationScheme = scheme; - Options.AutomaticAuthentication = true; + var handler = new CountHandler(); + var context = new Mock(); + context.Setup(c => c.Request).Returns(new Mock().Object); + context.Setup(c => c.Response).Returns(new Mock().Object); + await handler.InitializeAsync(new CountOptions(), context.Object, new LoggerFactory().CreateLogger("CountHandler"), Framework.WebEncoders.UrlEncoder.Default); + handler.Options.AuthenticationScheme = scheme; + handler.Options.AutomaticAuthentication = true; + return handler; } protected override Task HandleAuthenticateAsync() @@ -98,17 +109,24 @@ namespace Microsoft.AspNet.Authentication } - private class TestHandler : AuthenticationHandler + private class TestHandler : AuthenticationHandler { - public TestHandler(string scheme) + private TestHandler() { } + + public static async Task Create(string scheme) { - Initialize(new TestOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default); - Options.AuthenticationScheme = scheme; + var handler = new TestHandler(); + var context = new Mock(); + context.Setup(c => c.Request).Returns(new Mock().Object); + context.Setup(c => c.Response).Returns(new Mock().Object); + await handler.InitializeAsync(new TestOptions(), context.Object, new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default); + handler.Options.AuthenticationScheme = scheme; + return handler; } protected override Task HandleAuthenticateAsync() { - throw new NotImplementedException(); + return Task.FromResult(null); } } @@ -124,16 +142,23 @@ namespace Microsoft.AspNet.Authentication private class TestAutoHandler : AuthenticationHandler { - public TestAutoHandler(string scheme, bool auto) + private TestAutoHandler() { } + + public static async Task Create(string scheme, bool auto) { - Initialize(new TestAutoOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default); - Options.AuthenticationScheme = scheme; - Options.AutomaticAuthentication = auto; + var handler = new TestAutoHandler(); + var context = new Mock(); + context.Setup(c => c.Request).Returns(new Mock().Object); + context.Setup(c => c.Response).Returns(new Mock().Object); + await handler.InitializeAsync(new TestAutoOptions(), context.Object, new LoggerFactory().CreateLogger("TestAutoHandler"), Framework.WebEncoders.UrlEncoder.Default); + handler.Options.AuthenticationScheme = scheme; + handler.Options.AutomaticAuthentication = auto; + return handler; } protected override Task HandleAuthenticateAsync() { - throw new NotImplementedException(); + return Task.FromResult(null); } } } diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs index de511a994d..7136ce7f51 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs @@ -57,26 +57,16 @@ namespace Microsoft.AspNet.Authentication.Cookies } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri(bool auto) + [Fact] + public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri() { - var server = CreateServer(options => - { - options.LoginPath = new PathString("/login"); - options.AutomaticAuthentication = auto; - }); + var server = CreateServer(options => options.AutomaticAuthentication = true); var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect"); - // REVIEW: Now when Cookies are not in auto, noone handles the challenge so the Status stays OK, is that reasonable?? - transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.OK); - if (auto) - { - var location = transaction.Response.Headers.Location; - location.ToString().ShouldBe("http://example.com/login?ReturnUrl=%2FCustomRedirect"); - } + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); + var location = transaction.Response.Headers.Location; + location.ToString().ShouldBe("http://example.com/Account/Login?ReturnUrl=%2FCustomRedirect"); } private Task SignInAsAlice(HttpContext context) @@ -588,7 +578,7 @@ namespace Microsoft.AspNet.Authentication.Cookies [Theory] [InlineData(true)] [InlineData(false)] - public async Task CookieTurns401To403WithCookie(bool automatic) + public async Task CookieTurnsChallengeIntoForbidWithCookie(bool automatic) { var clock = new TestClock(); var server = CreateServer(options => @@ -603,7 +593,9 @@ namespace Microsoft.AspNet.Authentication.Cookies var url = "http://example.com/challenge"; var transaction2 = await SendAsync(server, url, transaction1.CookieNameValue); - transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); + var location = transaction2.Response.Headers.Location; + location.LocalPath.ShouldBe("/Account/AccessDenied"); } [Theory] @@ -614,9 +606,7 @@ namespace Microsoft.AspNet.Authentication.Cookies var clock = new TestClock(); var server = CreateServer(options => { - options.LoginPath = new PathString("/login"); options.AutomaticAuthentication = automatic; - options.AccessDeniedPath = new PathString("/accessdenied"); options.SystemClock = clock; }, SignInAsAlice); @@ -626,13 +616,13 @@ namespace Microsoft.AspNet.Authentication.Cookies transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); var location = transaction.Response.Headers.Location; - location.LocalPath.ShouldBe("/login"); + location.LocalPath.ShouldBe("/Account/Login"); } [Theory] [InlineData(true)] [InlineData(false)] - public async Task CookieForbidTurns401To403WithoutCookie(bool automatic) + public async Task CookieForbidRedirectsWithoutCookie(bool automatic) { var clock = new TestClock(); var server = CreateServer(options => @@ -645,7 +635,9 @@ namespace Microsoft.AspNet.Authentication.Cookies var url = "http://example.com/forbid"; var transaction = await SendAsync(server, url); - transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden); + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); + var location = transaction.Response.Headers.Location; + location.LocalPath.ShouldBe("/Account/AccessDenied"); } [Fact] @@ -670,19 +662,20 @@ namespace Microsoft.AspNet.Authentication.Cookies } [Fact] - public async Task CookieChallengeDoesNothingIfNotAuthenticated() + public async Task CookieChallengeRedirectsWithLoginPath() { var clock = new TestClock(); var server = CreateServer(options => { options.SystemClock = clock; + options.LoginPath = new PathString("/page"); }); var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction2 = await SendAsync(server, "http://example.com/challenge", transaction1.CookieNameValue); - transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); } [Fact] @@ -692,13 +685,14 @@ namespace Microsoft.AspNet.Authentication.Cookies var server = CreateServer(options => { options.SystemClock = clock; + options.LoginPath = new PathString("/page"); }); var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction2 = await SendAsync(server, "http://example.com/unauthorized", transaction1.CookieNameValue); - transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); } [Fact] @@ -720,6 +714,53 @@ namespace Microsoft.AspNet.Authentication.Cookies location.Query.ShouldBe("?ReturnUrl=%2F"); } + [Fact] + public async Task ChallengeDoesNotSet401OnUnauthorized() + { + var server = TestServer.Create(app => + { + app.UseCookieAuthentication(); + app.Run(async context => { + await Assert.ThrowsAsync(() => context.Authentication.ChallengeAsync()); + }); + }, services => services.AddAuthentication()); + + var transaction = await server.SendAsync("http://example.com"); + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + +/* [Fact] + public async Task UseCookieWithInstanceDoesntUseSharedOptions() + { + var server = TestServer.Create(app => + { + app.UseCookieAuthentication(options => options.CookieName = "One"); + app.UseCookieAuthentication(new CookieAuthenticationOptions()); + app.Run(context => context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity()))); + }, services => services.AddAuthentication()); + + var transaction = await server.SendAsync("http://example.com"); + + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.True(transaction.SetCookie[0].StartsWith(".AspNet.Cookies=")); + } + + [Fact] + public async Task UseCookieWithOutInstanceDoesUseSharedOptions() + { + var server = TestServer.Create(app => + { + app.UseCookieAuthentication(options => options.CookieName = "One"); + app.UseCookieAuthentication(options => options.AuthenticationScheme = "Two"); + app.Run(context => context.Authentication.SignInAsync("Two", new ClaimsPrincipal(new ClaimsIdentity()))); + }, services => services.AddAuthentication()); + + var transaction = await server.SendAsync("http://example.com"); + + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.True(transaction.SetCookie[0].StartsWith("One=")); + }*/ + [Fact] public async Task MapWithSignInOnlyRedirectToReturnUrlOnLoginPath() { @@ -870,6 +911,7 @@ namespace Microsoft.AspNet.Authentication.Cookies var server = TestServer.Create(app => { app.UseCookieAuthentication(configureOptions); + // app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = "Cookie2" }); if (claimsTransform != null) { @@ -961,17 +1003,13 @@ namespace Microsoft.AspNet.Authentication.Cookies } } - private static async Task SendAsync(TestServer server, string uri, string cookieHeader = null, bool ajaxRequest = false) + private static async Task SendAsync(TestServer server, string uri, string cookieHeader = null) { var request = new HttpRequestMessage(HttpMethod.Get, uri); if (!string.IsNullOrEmpty(cookieHeader)) { request.Headers.Add("Cookie", cookieHeader); } - if (ajaxRequest) - { - request.Headers.Add("X-Requested-With", "XMLHttpRequest"); - } var transaction = new Transaction { Request = request, diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs index 683f03563e..6aaf42bd39 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect AuthenticationProperties ISecureDataFormat.Unprotect(string protectedText) { - if (string.IsNullOrWhiteSpace(protectedText)) + if (string.IsNullOrEmpty(protectedText)) { return null; } diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectAuthenticationHandlerForTestingAuthenticate.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectAuthenticationHandlerForTestingAuthenticate.cs index 4a7759e2af..46c6d6f131 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectAuthenticationHandlerForTestingAuthenticate.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectAuthenticationHandlerForTestingAuthenticate.cs @@ -27,15 +27,6 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect return await base.HandleUnauthorizedAsync(context); } - protected override Task HandleSignInAsync(SignInContext context) - { - return Task.FromResult(0); - } - - protected override Task HandleSignOutAsync(SignOutContext context) - { - return Task.FromResult(0); - } protected override async Task RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri) { var jsonResponse = new JObject(); @@ -53,10 +44,5 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect claimsIdentity.AddClaim(new Claim("test claim", "test value")); return new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), ticket.Properties, ticket.AuthenticationScheme); } - - //public override bool ShouldHandleScheme(string authenticationScheme) - //{ - // return true; - //} } }