Fix for external logins disappearing after failed login (#7002)

Also adds external logins to register page
This commit is contained in:
Hao Kung 2019-03-04 12:46:18 -08:00 committed by GitHub
parent 368269c883
commit 48a67cfd18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 164 additions and 17 deletions

View File

@ -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<Microsoft.AspNetCore.Authentication.AuthenticationScheme> 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<Microsoft.AspNetCore.Mvc.IActionResult> 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<Microsoft.AspNetCore.Authentication.AuthenticationScheme> 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<Microsoft.AspNetCore.Mvc.IActionResult> OnPostAsync(string returnUrl = null) { throw null; }
public partial class InputModel
{

View File

@ -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

View File

@ -8,7 +8,7 @@
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
@ -27,9 +27,39 @@
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
<button id="registerSubmit" type="submit" class="btn btn-default">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {

View File

@ -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
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// 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.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// 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.
/// </summary>
public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException();
public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException();
/// <summary>
/// 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<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();

View File

@ -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

View File

@ -8,7 +8,7 @@
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
@ -27,9 +27,39 @@
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
<button id="registerSubmit" type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {

View File

@ -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
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// 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.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// 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.
/// </summary>
public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException();
public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException();
/// <summary>
/// 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<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();

View File

@ -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<AuthenticationScheme> 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<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser {

View File

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

View File

@ -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<Contoso.Login> 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<Index> SubmitRegisterFormForValidUserAsync(string userName, string password)

View File

@ -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()
{

View File

@ -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<Index> 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<Index> 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<Account.Manage.Index> SendEmailConfirmationLinkAsync(Index index)
{
var manage = await index.ClickManageLinkAsync();