Skip to content

Commit 48a67cf

Browse files
authored
Fix for external logins disappearing after failed login (#7002)
Also adds external logins to register page
1 parent 368269c commit 48a67cf

File tree

12 files changed

+164
-17
lines changed

12 files changed

+164
-17
lines changed

src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,11 @@ public void OnGet() { }
168168
public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel
169169
{
170170
protected RegisterModel() { }
171+
public System.Collections.Generic.IList<Microsoft.AspNetCore.Authentication.AuthenticationScheme> ExternalLogins { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
171172
[Microsoft.AspNetCore.Mvc.BindPropertyAttribute]
172173
public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.RegisterModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
173174
public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
174-
public virtual void OnGet(string returnUrl = null) { }
175+
public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; }
175176
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnPostAsync(string returnUrl = null) { throw null; }
176177
public partial class InputModel
177178
{
@@ -580,10 +581,11 @@ public void OnGet() { }
580581
public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel
581582
{
582583
protected RegisterModel() { }
584+
public System.Collections.Generic.IList<Microsoft.AspNetCore.Authentication.AuthenticationScheme> ExternalLogins { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
583585
[Microsoft.AspNetCore.Mvc.BindPropertyAttribute]
584586
public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.RegisterModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
585587
public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
586-
public virtual void OnGet(string returnUrl = null) { }
588+
public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; }
587589
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnPostAsync(string returnUrl = null) { throw null; }
588590
public partial class InputModel
589591
{

src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ public override async Task<IActionResult> OnPostAsync(string returnUrl = null)
124124
{
125125
returnUrl = returnUrl ?? Url.Content("~/");
126126

127+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
128+
127129
if (ModelState.IsValid)
128130
{
129131
// This doesn't count login failures towards account lockout

src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<div class="row">
1010
<div class="col-md-4">
11-
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
11+
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
1212
<h4>Create a new account.</h4>
1313
<hr />
1414
<div asp-validation-summary="All" class="text-danger"></div>
@@ -27,9 +27,39 @@
2727
<input asp-for="Input.ConfirmPassword" class="form-control" />
2828
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
2929
</div>
30-
<button type="submit" class="btn btn-default">Register</button>
30+
<button id="registerSubmit" type="submit" class="btn btn-default">Register</button>
3131
</form>
3232
</div>
33+
<div class="col-md-6 col-md-offset-2">
34+
<section>
35+
<h4>Use another service to register.</h4>
36+
<hr />
37+
@{
38+
if ((Model.ExternalLogins?.Count ?? 0) == 0)
39+
{
40+
<div>
41+
<p>
42+
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
43+
for details on setting up this ASP.NET application to support logging in via external services.
44+
</p>
45+
</div>
46+
}
47+
else
48+
{
49+
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
50+
<div>
51+
<p>
52+
@foreach (var provider in Model.ExternalLogins)
53+
{
54+
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
55+
}
56+
</p>
57+
</div>
58+
</form>
59+
}
60+
}
61+
</section>
62+
</div>
3363
</div>
3464

3565
@section Scripts {

src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.ComponentModel.DataAnnotations;
7+
using System.Linq;
68
using System.Text.Encodings.Web;
79
using System.Threading;
810
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Authentication;
912
using Microsoft.AspNetCore.Authorization;
1013
using Microsoft.AspNetCore.Identity.UI.Services;
1114
using Microsoft.AspNetCore.Mvc;
@@ -36,6 +39,12 @@ public abstract class RegisterModel : PageModel
3639
/// </summary>
3740
public string ReturnUrl { get; set; }
3841

42+
/// <summary>
43+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
44+
/// directly from your code. This API may change or be removed in future releases.
45+
/// </summary>
46+
public IList<AuthenticationScheme> ExternalLogins { get; set; }
47+
3948
/// <summary>
4049
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
4150
/// directly from your code. This API may change or be removed in future releases.
@@ -75,7 +84,7 @@ public class InputModel
7584
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
7685
/// directly from your code. This API may change or be removed in future releases.
7786
/// </summary>
78-
public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException();
87+
public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException();
7988

8089
/// <summary>
8190
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
@@ -108,14 +117,16 @@ public RegisterModel(
108117
_emailSender = emailSender;
109118
}
110119

111-
public override void OnGet(string returnUrl = null)
120+
public override async Task OnGetAsync(string returnUrl = null)
112121
{
113122
ReturnUrl = returnUrl;
123+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
114124
}
115125

116126
public override async Task<IActionResult> OnPostAsync(string returnUrl = null)
117127
{
118128
returnUrl = returnUrl ?? Url.Content("~/");
129+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
119130
if (ModelState.IsValid)
120131
{
121132
var user = CreateUser();

src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ public override async Task<IActionResult> OnPostAsync(string returnUrl = null)
123123
{
124124
returnUrl = returnUrl ?? Url.Content("~/");
125125

126+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
127+
126128
if (ModelState.IsValid)
127129
{
128130
// This doesn't count login failures towards account lockout

src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<div class="row">
1010
<div class="col-md-4">
11-
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
11+
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
1212
<h4>Create a new account.</h4>
1313
<hr />
1414
<div asp-validation-summary="All" class="text-danger"></div>
@@ -27,9 +27,39 @@
2727
<input asp-for="Input.ConfirmPassword" class="form-control" />
2828
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
2929
</div>
30-
<button type="submit" class="btn btn-primary">Register</button>
30+
<button id="registerSubmit" type="submit" class="btn btn-primary">Register</button>
3131
</form>
3232
</div>
33+
<div class="col-md-6 col-md-offset-2">
34+
<section>
35+
<h4>Use another service to register.</h4>
36+
<hr />
37+
@{
38+
if ((Model.ExternalLogins?.Count ?? 0) == 0)
39+
{
40+
<div>
41+
<p>
42+
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
43+
for details on setting up this ASP.NET application to support logging in via external services.
44+
</p>
45+
</div>
46+
}
47+
else
48+
{
49+
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
50+
<div>
51+
<p>
52+
@foreach (var provider in Model.ExternalLogins)
53+
{
54+
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
55+
}
56+
</p>
57+
</div>
58+
</form>
59+
}
60+
}
61+
</section>
62+
</div>
3363
</div>
3464

3565
@section Scripts {

src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.ComponentModel.DataAnnotations;
7+
using System.Linq;
68
using System.Text.Encodings.Web;
79
using System.Threading;
810
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Authentication;
912
using Microsoft.AspNetCore.Authorization;
1013
using Microsoft.AspNetCore.Identity.UI.Services;
1114
using Microsoft.AspNetCore.Mvc;
@@ -35,6 +38,12 @@ public abstract class RegisterModel : PageModel
3538
/// </summary>
3639
public string ReturnUrl { get; set; }
3740

41+
/// <summary>
42+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
43+
/// directly from your code. This API may change or be removed in future releases.
44+
/// </summary>
45+
public IList<AuthenticationScheme> ExternalLogins { get; set; }
46+
3847
/// <summary>
3948
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
4049
/// directly from your code. This API may change or be removed in future releases.
@@ -74,7 +83,7 @@ public class InputModel
7483
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
7584
/// directly from your code. This API may change or be removed in future releases.
7685
/// </summary>
77-
public virtual void OnGet(string returnUrl = null) => throw new NotImplementedException();
86+
public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException();
7887

7988
/// <summary>
8089
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
@@ -107,14 +116,16 @@ public RegisterModel(
107116
_emailSender = emailSender;
108117
}
109118

110-
public override void OnGet(string returnUrl = null)
119+
public override async Task OnGetAsync(string returnUrl = null)
111120
{
112121
ReturnUrl = returnUrl;
122+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
113123
}
114124

115125
public override async Task<IActionResult> OnPostAsync(string returnUrl = null)
116126
{
117127
returnUrl = returnUrl ?? Url.Content("~/");
128+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
118129
if (ModelState.IsValid)
119130
{
120131
var user = CreateUser();

src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.ComponentModel.DataAnnotations;
7+
using System.Linq;
68
using System.Text.Encodings.Web;
79
using System.Threading.Tasks;
810
using IdentitySample.DefaultUI.Data;
11+
using Microsoft.AspNetCore.Authentication;
912
using Microsoft.AspNetCore.Identity;
1013
using Microsoft.AspNetCore.Identity.UI.Services;
1114
using Microsoft.AspNetCore.Mvc;
@@ -38,6 +41,8 @@ public RegisterModel(
3841

3942
public string ReturnUrl { get; set; }
4043

44+
public IList<AuthenticationScheme> ExternalLogins { get; set; }
45+
4146
public class InputModel
4247
{
4348
[Required]
@@ -67,14 +72,16 @@ public class InputModel
6772
public int Age { get; set; }
6873
}
6974

70-
public void OnGet(string returnUrl = null)
75+
public async Task OnGetAsync(string returnUrl = null)
7176
{
7277
ReturnUrl = returnUrl;
78+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
7379
}
7480

7581
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
7682
{
7783
returnUrl = returnUrl ?? Url.Content("~/");
84+
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
7885
if (ModelState.IsValid)
7986
{
8087
var user = new ApplicationUser {

src/Identity/test/Identity.FunctionalTests/ManagementTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;

src/Identity/test/Identity.FunctionalTests/Pages/Account/Register.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
@@ -12,11 +12,29 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
1212
public class Register : DefaultUIPage
1313
{
1414
private IHtmlFormElement _registerForm;
15+
private IHtmlFormElement _externalLoginForm;
16+
private readonly IHtmlElement _contosoButton;
1517

1618
public Register(HttpClient client, IHtmlDocument register, DefaultUIContext context)
1719
: base(client, register, context)
1820
{
19-
_registerForm = HtmlAssert.HasForm(register);
21+
_registerForm = HtmlAssert.HasForm("#registerForm", register);
22+
if (context.ContosoLoginEnabled)
23+
{
24+
_externalLoginForm = HtmlAssert.HasForm("#external-account", register);
25+
_contosoButton = HtmlAssert.HasElement("button[value=Contoso]", register);
26+
}
27+
}
28+
29+
public async Task<Contoso.Login> ClickLoginWithContosoLinkAsync()
30+
{
31+
var externalFormResponse = await Client.SendAsync(_externalLoginForm, _contosoButton);
32+
var goToContosoLogin = ResponseAssert.IsRedirect(externalFormResponse);
33+
var contosoLoginResponse = await Client.GetAsync(goToContosoLogin);
34+
35+
var contosoLogin = await ResponseAssert.IsHtmlDocumentAsync(contosoLoginResponse);
36+
37+
return new Contoso.Login(Client, contosoLogin, Context);
2038
}
2139

2240
public async Task<Index> SubmitRegisterFormForValidUserAsync(string userName, string password)

src/Identity/test/Identity.FunctionalTests/RegistrationTests.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -56,7 +56,7 @@ void ConfigureTestServices(IServiceCollection services) =>
5656
}
5757

5858
[Fact]
59-
public async Task CanRegisterWithASocialLoginProvider()
59+
public async Task CanRegisterWithASocialLoginProviderFromLogin()
6060
{
6161
// Arrange
6262
void ConfigureTestServices(IServiceCollection services) =>
@@ -75,6 +75,26 @@ void ConfigureTestServices(IServiceCollection services) =>
7575
await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email);
7676
}
7777

78+
[Fact]
79+
public async Task CanRegisterWithASocialLoginProviderFromRegister()
80+
{
81+
// Arrange
82+
void ConfigureTestServices(IServiceCollection services) =>
83+
services
84+
.SetupTestThirdPartyLogin();
85+
86+
var client = ServerFactory
87+
.WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices))
88+
.CreateClient();
89+
90+
var guid = Guid.NewGuid();
91+
var userName = $"{guid}";
92+
var email = $"{guid}@example.com";
93+
94+
// Act & Assert
95+
await UserStories.RegisterNewUserWithSocialLoginAsyncViaRegisterPage(client, userName, email);
96+
}
97+
7898
[Fact]
7999
public async Task CanRegisterWithASocialLoginProvider_WithGlobalAuthorizeFilter()
80100
{

0 commit comments

Comments
 (0)