From efe0c6be7b1847b2fa3f774ef85de5c8aa4493d4 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 13 Mar 2018 13:11:29 -0700 Subject: [PATCH] Split Auth into AddIdentityCookies (#1442) --- Identity.sln | 2 +- samples/IdentitySample.DefaultUI/Startup.cs | 20 +++- .../IdentitySample.Mvc.csproj | 1 + samples/IdentitySample.Mvc/Startup.cs | 13 ++- .../IdentityServiceCollectionExtensions.cs | 9 ++ src/Identity/IdentityBuilderExtensions.cs | 22 +++- src/Identity/IdentityConstants.cs | 2 +- src/Identity/IdentityCookiesBuilder.cs | 34 ++++++ .../IdentityCookiesBuilderExtensions.cs | 103 ++++++++++++++++++ .../IdentityServiceCollectionExtensions.cs | 44 ++++---- src/UI/IdentityBuilderUIExtensions.cs | 1 + test/Identity.Test/IdentityOptionsTest.cs | 27 ++++- .../SecurityStampValidatorTest.cs | 1 - .../Identity.DefaultUI.WebSite/Startup.cs | 10 +- 14 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 src/Identity/IdentityCookiesBuilder.cs create mode 100644 src/Identity/IdentityCookiesBuilderExtensions.cs diff --git a/Identity.sln b/Identity.sln index aa69c50755..4bfe34fdd3 100644 --- a/Identity.sln +++ b/Identity.sln @@ -965,4 +965,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B3F2A592-CCE0-40C2-8CA4-7B1293DED874} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/samples/IdentitySample.DefaultUI/Startup.cs b/samples/IdentitySample.DefaultUI/Startup.cs index 505c7f0490..cfa5dfcae1 100644 --- a/samples/IdentitySample.DefaultUI/Startup.cs +++ b/samples/IdentitySample.DefaultUI/Startup.cs @@ -2,7 +2,6 @@ using IdentitySample.DefaultUI.Data; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -33,14 +32,23 @@ namespace IdentitySample.DefaultUI options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), x => x.MigrationsAssembly("IdentitySample.DefaultUI"))); - services.AddIdentity(o => o.Stores.MaxLengthForKeys = 128) - .AddEntityFrameworkStores() - .AddDefaultUI() - .AddDefaultTokenProviders(); - services.AddMvc(); + + services.AddIdentityCore(o => o.Stores.MaxLengthForKeys = 128) + .AddRoles() + .AddEntityFrameworkStores() + .AddDefaultUI() + .AddDefaultTokenProviders(); + + services.AddAuthentication(o => + { + o.DefaultScheme = IdentityConstants.ApplicationScheme; + o.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(o => { }); } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { diff --git a/samples/IdentitySample.Mvc/IdentitySample.Mvc.csproj b/samples/IdentitySample.Mvc/IdentitySample.Mvc.csproj index 9db4b00740..07074aac42 100644 --- a/samples/IdentitySample.Mvc/IdentitySample.Mvc.csproj +++ b/samples/IdentitySample.Mvc/IdentitySample.Mvc.csproj @@ -8,6 +8,7 @@ + diff --git a/samples/IdentitySample.Mvc/Startup.cs b/samples/IdentitySample.Mvc/Startup.cs index 4fd452f34b..6f6134a62e 100644 --- a/samples/IdentitySample.Mvc/Startup.cs +++ b/samples/IdentitySample.Mvc/Startup.cs @@ -32,11 +32,20 @@ namespace IdentitySample services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); - services.AddIdentity() + services.AddMvc(); + + services.AddIdentityCore() + .AddRoles() .AddEntityFrameworkStores() + .AddSignInManager() .AddDefaultTokenProviders(); - services.AddMvc(); + services.AddAuthentication(o => + { + o.DefaultScheme = IdentityConstants.ApplicationScheme; + o.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(o => { }); // Add application services. services.AddTransient(); diff --git a/src/Core/IdentityServiceCollectionExtensions.cs b/src/Core/IdentityServiceCollectionExtensions.cs index 258a515a48..b4730bcf59 100644 --- a/src/Core/IdentityServiceCollectionExtensions.cs +++ b/src/Core/IdentityServiceCollectionExtensions.cs @@ -12,6 +12,15 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class IdentityServiceCollectionExtensions { + /// + /// Adds and configures the identity system for the specified User and Role types. + /// + /// The type representing a User in the system. + /// The services available in the application. + /// An for creating and configuring the identity system. + public static IdentityBuilder AddIdentityCore(this IServiceCollection services) where TUser : class + => services.AddIdentityCore(o => { }); + /// /// Adds and configures the identity system for the specified User and Role types. /// diff --git a/src/Identity/IdentityBuilderExtensions.cs b/src/Identity/IdentityBuilderExtensions.cs index 6fa60725a5..df0a1c59d0 100644 --- a/src/Identity/IdentityBuilderExtensions.cs +++ b/src/Identity/IdentityBuilderExtensions.cs @@ -31,6 +31,26 @@ namespace Microsoft.AspNetCore.Identity .AddTokenProvider(TokenOptions.DefaultAuthenticatorProvider, authenticatorProviderType); } + private static void AddSignInManagerDeps(this IdentityBuilder builder) + { + builder.Services.AddHttpContextAccessor(); + builder.Services.AddScoped(typeof(ISecurityStampValidator), typeof(SecurityStampValidator<>).MakeGenericType(builder.UserType)); + builder.Services.AddScoped(typeof(ITwoFactorSecurityStampValidator), typeof(TwoFactorSecurityStampValidator<>).MakeGenericType(builder.UserType)); + } + + /// + /// Adds a for the . + /// + /// The current instance. + /// The current instance. + public static IdentityBuilder AddSignInManager(this IdentityBuilder builder) + { + builder.AddSignInManagerDeps(); + var managerType = typeof(SignInManager<>).MakeGenericType(builder.UserType); + builder.Services.AddScoped(managerType); + return builder; + } + /// /// Adds a for the . /// @@ -39,8 +59,8 @@ namespace Microsoft.AspNetCore.Identity /// The current instance. public static IdentityBuilder AddSignInManager(this IdentityBuilder builder) where TSignInManager : class { + builder.AddSignInManagerDeps(); var managerType = typeof(SignInManager<>).MakeGenericType(builder.UserType); - var customType = typeof(TSignInManager); if (!managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) { diff --git a/src/Identity/IdentityConstants.cs b/src/Identity/IdentityConstants.cs index e01715dfbc..0843a33bd4 100644 --- a/src/Identity/IdentityConstants.cs +++ b/src/Identity/IdentityConstants.cs @@ -4,7 +4,7 @@ namespace Microsoft.AspNetCore.Identity { /// - /// Represents all the options you can use to configure the cookies middleware used by the identity system. + /// Represents all the options you can use to configure the cookies middleware uesd by the identity system. /// public class IdentityConstants { diff --git a/src/Identity/IdentityCookiesBuilder.cs b/src/Identity/IdentityCookiesBuilder.cs new file mode 100644 index 0000000000..9ceebf823e --- /dev/null +++ b/src/Identity/IdentityCookiesBuilder.cs @@ -0,0 +1,34 @@ +// 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.Authentication.Cookies; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Identity +{ + /// + /// Used to configure identity cookie options. + /// + public class IdentityCookiesBuilder + { + /// + /// Used to configure the application cookie. + /// + public OptionsBuilder ApplicationCookie { get; set; } + + /// + /// Used to configure the external cookie. + /// + public OptionsBuilder ExternalCookie { get; set; } + + /// + /// Used to configure the two factor remember me cookie. + /// + public OptionsBuilder TwoFactorRememberMeCookie { get; set; } + + /// + /// Used to configure the two factor user id cookie. + /// + public OptionsBuilder TwoFactorUserIdCookie { get; set; } + } +} diff --git a/src/Identity/IdentityCookiesBuilderExtensions.cs b/src/Identity/IdentityCookiesBuilderExtensions.cs new file mode 100644 index 0000000000..65d32d337b --- /dev/null +++ b/src/Identity/IdentityCookiesBuilderExtensions.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.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Identity +{ + /// + /// Helper functions for configuring identity services. + /// + public static class IdentityCookieAuthenticationBuilderExtensions + { + /// + /// Adds cookie authentication. + /// + /// The current instance. + /// The which can be used to configure the identity cookies. + public static IdentityCookiesBuilder AddIdentityCookies(this AuthenticationBuilder builder) + => builder.AddIdentityCookies(o => { }); + + /// + /// Adds the cookie authentication needed for sign in manager. + /// + /// The current instance. + /// Action used to configure the cookies. + /// The which can be used to configure the identity cookies. + public static IdentityCookiesBuilder AddIdentityCookies(this AuthenticationBuilder builder, Action configureCookies) + { + var cookieBuilder = new IdentityCookiesBuilder(); + cookieBuilder.ApplicationCookie = builder.AddApplicationCookie(); + cookieBuilder.ExternalCookie = builder.AddExternalCookie(); + cookieBuilder.TwoFactorRememberMeCookie = builder.AddTwoFactorRememberMeCookie(); + cookieBuilder.TwoFactorUserIdCookie = builder.AddTwoFactorUserIdCookie(); + configureCookies?.Invoke(cookieBuilder); + return cookieBuilder; + } + + /// + /// Adds the identity application cookie. + /// + /// The current instance. + /// The which can be used to configure the cookie authentication. + public static OptionsBuilder AddApplicationCookie(this AuthenticationBuilder builder) + { + builder.AddCookie(IdentityConstants.ApplicationScheme, o => + { + o.LoginPath = new PathString("/Account/Login"); + o.Events = new CookieAuthenticationEvents + { + OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync + }; + }); + return new OptionsBuilder(builder.Services, IdentityConstants.ApplicationScheme); + } + + /// + /// Adds the identity cookie used for external logins. + /// + /// The current instance. + /// The which can be used to configure the cookie authentication. + public static OptionsBuilder AddExternalCookie(this AuthenticationBuilder builder) + { + builder.AddCookie(IdentityConstants.ExternalScheme, o => + { + o.Cookie.Name = IdentityConstants.ExternalScheme; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }); + return new OptionsBuilder(builder.Services, IdentityConstants.ExternalScheme); + } + + /// + /// Adds the identity cookie used for two factor remember me. + /// + /// The current instance. + /// The which can be used to configure the cookie authentication. + public static OptionsBuilder AddTwoFactorRememberMeCookie(this AuthenticationBuilder builder) + { + builder.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme); + return new OptionsBuilder(builder.Services, IdentityConstants.TwoFactorRememberMeScheme); + } + + /// + /// Adds the identity cookie used for two factor logins. + /// + /// The current instance. + /// The which can be used to configure the cookie authentication. + public static OptionsBuilder AddTwoFactorUserIdCookie(this AuthenticationBuilder builder) + { + builder.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o => + { + o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }); + return new OptionsBuilder(builder.Services, IdentityConstants.TwoFactorUserIdScheme); + } + } +} diff --git a/src/Identity/IdentityServiceCollectionExtensions.cs b/src/Identity/IdentityServiceCollectionExtensions.cs index d81f1b721b..c941fdd201 100644 --- a/src/Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Identity/IdentityServiceCollectionExtensions.cs @@ -2,12 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection @@ -28,27 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection this IServiceCollection services) where TUser : class where TRole : class - { - return services.AddIdentity(setupAction: null); - } - - /// - /// Configures the application cookie. - /// - /// The services available in the application. - /// An action to configure the . - /// The services. - public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action configure) - => services.Configure(IdentityConstants.ApplicationScheme, configure); - - /// - /// Configure the external cookie. - /// - /// The services available in the application. - /// An action to configure the . - /// The services. - public static IServiceCollection ConfigureExternalCookie(this IServiceCollection services, Action configure) - => services.Configure(IdentityConstants.ExternalScheme, configure); + => services.AddIdentity(setupAction: null); /// /// Adds and configures the identity system for the specified User and Role types. @@ -122,5 +99,24 @@ namespace Microsoft.Extensions.DependencyInjection return new IdentityBuilder(typeof(TUser), typeof(TRole), services); } + + /// + /// Configures the application cookie. + /// + /// The services available in the application. + /// An action to configure the . + /// The services. + public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action configure) + => services.Configure(IdentityConstants.ApplicationScheme, configure); + + /// + /// Configure the external cookie. + /// + /// The services available in the application. + /// An action to configure the . + /// The services. + public static IServiceCollection ConfigureExternalCookie(this IServiceCollection services, Action configure) + => services.Configure(IdentityConstants.ExternalScheme, configure); + } } diff --git a/src/UI/IdentityBuilderUIExtensions.cs b/src/UI/IdentityBuilderUIExtensions.cs index 96d9e28c1d..9e276d5ad9 100644 --- a/src/UI/IdentityBuilderUIExtensions.cs +++ b/src/UI/IdentityBuilderUIExtensions.cs @@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Identity /// The . public static IdentityBuilder AddDefaultUI(this IdentityBuilder builder) { + builder.AddSignInManager(); AddAdditionalApplicationParts(builder); builder.Services.ConfigureOptions( diff --git a/test/Identity.Test/IdentityOptionsTest.cs b/test/Identity.Test/IdentityOptionsTest.cs index d5dde4190b..400ea15d21 100644 --- a/test/Identity.Test/IdentityOptionsTest.cs +++ b/test/Identity.Test/IdentityOptionsTest.cs @@ -2,10 +2,8 @@ // 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.Security.Claims; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Xunit; @@ -71,5 +69,28 @@ namespace Microsoft.AspNetCore.Identity.Test var myOptions = optionsGetter.Value; Assert.True(myOptions.User.RequireUniqueEmail); } + + [Fact] + public void CanConfigureCookieOptions() + { + var services = new ServiceCollection(); + services.AddAuthentication().AddIdentityCookies(o => + { + o.ApplicationCookie.Configure(a => a.Cookie.Name = "a"); + o.ExternalCookie.Configure(a => a.Cookie.Name = "b"); + o.TwoFactorRememberMeCookie.Configure(a => a.Cookie.Name = "c"); + o.TwoFactorUserIdCookie.Configure(a => a.Cookie.Name = "d"); + }); + var serviceProvider = services.BuildServiceProvider(); + + var options = serviceProvider.GetRequiredService>(); + Assert.NotNull(options); + + Assert.Equal("a", options.Get(IdentityConstants.ApplicationScheme).Cookie.Name); + Assert.Equal("b", options.Get(IdentityConstants.ExternalScheme).Cookie.Name); + Assert.Equal("c", options.Get(IdentityConstants.TwoFactorRememberMeScheme).Cookie.Name); + Assert.Equal("d", options.Get(IdentityConstants.TwoFactorUserIdScheme).Cookie.Name); + } + } } \ No newline at end of file diff --git a/test/Identity.Test/SecurityStampValidatorTest.cs b/test/Identity.Test/SecurityStampValidatorTest.cs index 5b6b76a9e3..a4634dd1bf 100644 --- a/test/Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Identity.Test/SecurityStampValidatorTest.cs @@ -6,7 +6,6 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Startup.cs b/test/WebSites/Identity.DefaultUI.WebSite/Startup.cs index a906fb3966..95231feabd 100644 --- a/test/WebSites/Identity.DefaultUI.WebSite/Startup.cs +++ b/test/WebSites/Identity.DefaultUI.WebSite/Startup.cs @@ -35,11 +35,19 @@ namespace Identity.DefaultUI.WebSite sqlOptions => sqlOptions.MigrationsAssembly("Identity.DefaultUI.WebSite") )); - services.AddIdentity() + services.AddIdentityCore() + .AddRoles() .AddEntityFrameworkStores() .AddDefaultUI() .AddDefaultTokenProviders(); + services.AddAuthentication(o => + { + o.DefaultScheme = IdentityConstants.ApplicationScheme; + o.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(o => {}); + services.AddMvc() .AddRazorPagesOptions(options => {