From 48a67cfd1833d3f283d6d75e74e08b0b5bf6a8ef Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 4 Mar 2019 12:46:18 -0800 Subject: [PATCH] Fix for external logins disappearing after failed login (#7002) Also adds external logins to register page --- ...ft.AspNetCore.Identity.UI.netcoreapp3.0.cs | 6 ++-- .../Identity/Pages/V3/Account/Login.cshtml.cs | 2 ++ .../Identity/Pages/V3/Account/Register.cshtml | 34 +++++++++++++++++-- .../Pages/V3/Account/Register.cshtml.cs | 15 ++++++-- .../Identity/Pages/V4/Account/Login.cshtml.cs | 2 ++ .../Identity/Pages/V4/Account/Register.cshtml | 34 +++++++++++++++++-- .../Pages/V4/Account/Register.cshtml.cs | 15 ++++++-- .../Identity/Pages/Account/Register.cshtml.cs | 9 ++++- .../ManagementTests.cs | 2 +- .../Pages/Account/Register.cs | 22 ++++++++++-- .../RegistrationTests.cs | 24 +++++++++++-- .../Identity.FunctionalTests/UserStories.cs | 16 ++++++++- 12 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs index bb3d932c81..462e9f8800 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs @@ -168,10 +168,11 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected RegisterModel() { } + public System.Collections.Generic.IList ExternalLogins { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.RegisterModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual void OnGet(string returnUrl = null) { } + public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync(string returnUrl = null) { throw null; } public partial class InputModel { @@ -580,10 +581,11 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected RegisterModel() { } + public System.Collections.Generic.IList ExternalLogins { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.RegisterModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual void OnGet(string returnUrl = null) { } + public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync(string returnUrl = null) { throw null; } public partial class InputModel { diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs index f205643186..9f5dc83c5e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs @@ -124,6 +124,8 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal { returnUrl = returnUrl ?? Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + if (ModelState.IsValid) { // This doesn't count login failures towards account lockout diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml index d40f55c35c..cf6a7c6e96 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml @@ -8,7 +8,7 @@
-
+

Create a new account.


@@ -27,9 +27,39 @@
- +
+
+
+

Use another service to register.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins) + { + + } +

+
+
+ } + } +
+
@section Scripts { diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml.cs index af35c3e120..3f66acc200 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml.cs @@ -2,10 +2,13 @@ // 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.ComponentModel.DataAnnotations; +using System.Linq; using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; @@ -36,6 +39,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal /// public string ReturnUrl { get; set; } + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList ExternalLogins { get; set; } + /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -75,7 +84,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException(); + public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException(); /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used @@ -108,14 +117,16 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal _emailSender = emailSender; } - public override void OnGet(string returnUrl = null) + public override async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public override async Task OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml.cs index 96afc0b735..8b21750186 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml.cs @@ -123,6 +123,8 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal { returnUrl = returnUrl ?? Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + if (ModelState.IsValid) { // This doesn't count login failures towards account lockout diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml index 226291193e..99620a5836 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml @@ -8,7 +8,7 @@
-
+

Create a new account.


@@ -27,9 +27,39 @@
- +
+
+
+

Use another service to register.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins) + { + + } +

+
+
+ } + } +
+
@section Scripts { diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml.cs index de8322f63f..04d3d034db 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml.cs @@ -2,10 +2,13 @@ // 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.ComponentModel.DataAnnotations; +using System.Linq; using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; @@ -35,6 +38,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal ///
public string ReturnUrl { get; set; } + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList ExternalLogins { get; set; } + /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -74,7 +83,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException(); + public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException(); /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used @@ -107,14 +116,16 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal _emailSender = emailSender; } - public override void OnGet(string returnUrl = null) + public override async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public override async Task OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); diff --git a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs index 35cb402bb8..bbe8394abb 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -2,10 +2,13 @@ // 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.ComponentModel.DataAnnotations; +using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using IdentitySample.DefaultUI.Data; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; @@ -38,6 +41,8 @@ namespace IdentitySample.DefaultUI public string ReturnUrl { get; set; } + public IList ExternalLogins { get; set; } + public class InputModel { [Required] @@ -67,14 +72,16 @@ namespace IdentitySample.DefaultUI public int Age { get; set; } } - public void OnGet(string returnUrl = null) + public async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public async Task OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = new ApplicationUser { diff --git a/src/Identity/test/Identity.FunctionalTests/ManagementTests.cs b/src/Identity/test/Identity.FunctionalTests/ManagementTests.cs index b2ef750f94..b2faff4f04 100644 --- a/src/Identity/test/Identity.FunctionalTests/ManagementTests.cs +++ b/src/Identity/test/Identity.FunctionalTests/ManagementTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Register.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Register.cs index 41afb55fc6..e4a846781c 100644 --- a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Register.cs +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Register.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Collections.Generic; @@ -12,11 +12,29 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account public class Register : DefaultUIPage { private IHtmlFormElement _registerForm; + private IHtmlFormElement _externalLoginForm; + private readonly IHtmlElement _contosoButton; public Register(HttpClient client, IHtmlDocument register, DefaultUIContext context) : base(client, register, context) { - _registerForm = HtmlAssert.HasForm(register); + _registerForm = HtmlAssert.HasForm("#registerForm", register); + if (context.ContosoLoginEnabled) + { + _externalLoginForm = HtmlAssert.HasForm("#external-account", register); + _contosoButton = HtmlAssert.HasElement("button[value=Contoso]", register); + } + } + + public async Task ClickLoginWithContosoLinkAsync() + { + var externalFormResponse = await Client.SendAsync(_externalLoginForm, _contosoButton); + var goToContosoLogin = ResponseAssert.IsRedirect(externalFormResponse); + var contosoLoginResponse = await Client.GetAsync(goToContosoLogin); + + var contosoLogin = await ResponseAssert.IsHtmlDocumentAsync(contosoLoginResponse); + + return new Contoso.Login(Client, contosoLogin, Context); } public async Task SubmitRegisterFormForValidUserAsync(string userName, string password) diff --git a/src/Identity/test/Identity.FunctionalTests/RegistrationTests.cs b/src/Identity/test/Identity.FunctionalTests/RegistrationTests.cs index 0ad2a32858..242ba5de6f 100644 --- a/src/Identity/test/Identity.FunctionalTests/RegistrationTests.cs +++ b/src/Identity/test/Identity.FunctionalTests/RegistrationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests } [Fact] - public async Task CanRegisterWithASocialLoginProvider() + public async Task CanRegisterWithASocialLoginProviderFromLogin() { // Arrange void ConfigureTestServices(IServiceCollection services) => @@ -75,6 +75,26 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email); } + [Fact] + public async Task CanRegisterWithASocialLoginProviderFromRegister() + { + // Arrange + void ConfigureTestServices(IServiceCollection services) => + services + .SetupTestThirdPartyLogin(); + + var client = ServerFactory + .WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices)) + .CreateClient(); + + var guid = Guid.NewGuid(); + var userName = $"{guid}"; + var email = $"{guid}@example.com"; + + // Act & Assert + await UserStories.RegisterNewUserWithSocialLoginAsyncViaRegisterPage(client, userName, email); + } + [Fact] public async Task CanRegisterWithASocialLoginProvider_WithGlobalAuthorizeFilter() { diff --git a/src/Identity/test/Identity.FunctionalTests/UserStories.cs b/src/Identity/test/Identity.FunctionalTests/UserStories.cs index 96c5d496d2..a225cec1b6 100644 --- a/src/Identity/test/Identity.FunctionalTests/UserStories.cs +++ b/src/Identity/test/Identity.FunctionalTests/UserStories.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -55,6 +55,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests return await login.LockoutUserAsync(userName, password); } + // This is via login page internal static async Task RegisterNewUserWithSocialLoginAsync(HttpClient client, string userName, string email) { var index = await Index.CreateAsync(client, new DefaultUIContext().WithSocialLoginEnabled()); @@ -68,6 +69,19 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests return await externalLogin.SendEmailAsync(email); } + internal static async Task RegisterNewUserWithSocialLoginAsyncViaRegisterPage(HttpClient client, string userName, string email) + { + var index = await Index.CreateAsync(client, new DefaultUIContext().WithSocialLoginEnabled()); + + var register = await index.ClickRegisterLinkAsync(); + + var contosoLogin = await register.ClickLoginWithContosoLinkAsync(); + + var externalLogin = await contosoLogin.SendNewUserNameAsync(userName); + + return await externalLogin.SendEmailAsync(email); + } + internal static async Task SendEmailConfirmationLinkAsync(Index index) { var manage = await index.ClickManageLinkAsync();