ASP.NET Core 2.2 - Scaffold Identity UI
Ken Haggerty
Created 01/30/2019 - Updated 02/21/2019 23:33The article I wrote for Code Project Require Confirmed Email in ASP.NET Core 2.2 - Part 1 briefly describes scaffolding Identity UI. This article will cover just scaffolding Identity UI and provide solutions to a couple of layout issues. I will assume you have created a new ASP.NET Core 2.2 Razor Pages project with Individual User Accounts and updated the database with the CreateIdentitySchema migration.
Scaffold Identity
Right click the project name > Add > New Scaffolded Item.
Select Identity in the left menu.
Check Override all files and select ApplicationDbContext.
Edit Startup.cs > ConfigureServices to use AddIdentity<IdentityUser, IdentityRole> instead of AddDefaultIdentity<IdentityUser>.
//services.AddDefaultIdentity<IdentityUser>() services.AddIdentity<IdentityUser, IdentityRole>() .AddDefaultUI(UIFramework.Bootstrap4) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddRazorPagesOptions(options => { options.AllowAreas = true; options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage"); options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout"); }); services.ConfigureApplicationCookie(options => { options.LoginPath = $"/Identity/Account/Login"; options.LogoutPath = $"/Identity/Account/Logout"; options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; });
Section references:
Test the project, it should build and run. You should be able to register a user but there are a few issues which work in the Default Identity UI class but are not scaffolded. I found some of these issues have been addressed in the source code on GitHub but have not reached the templating feature.
First the logout behavior which I documented in ASP.NET Core 2.2 - Razor Pages Logout Behavior.
Edit Logout.cshtml.cs > OnPost() to refresh the Logout page.
public async Task<IActionResult> OnPost(string returnUrl = null) { await _signInManager.SignOutAsync(); _logger.LogInformation("User logged out."); if (returnUrl != null) { return LocalRedirect(returnUrl); } else { //return Page(); return RedirectToPage(); } }
With the Default Identity UI Class, before you accept the cookie banner, the TwoFactorAuthentication page under Manage your account displays a warning (navigate to Identity/Account/Manage/ by clicking Hello USERNAME! then click the Two-factor authentication).
The scaffolded page doesn't have this feature yet, but you should add it.
Edit TwoFactorAuthentication.cshtml.
@page @using Microsoft.AspNetCore.Http.Features @model TwoFactorAuthenticationModel @{ ViewData["Title"]="Two-factor authentication(2FA)"; ViewData["ActivePage"]=ManageNavPages.TwoFactorAuthentication; } <partial name="_StatusMessage" for="StatusMessage" /> <h4>@ViewData["Title"]</h4> @{ var consentFeature=HttpContext.Features.Get<ITrackingConsentFeature>(); @if(consentFeature?.CanTrack ?? true) { @if(Model.Is2faEnabled) { if (Model.RecoveryCodesLeft == 0) { <div class="alert alert-danger"> <strong>You have no recovery codes left.</strong> <p>You must <a asp-page= "./GenerateRecoveryCodes" > generate a new set of recovery codes</a> before you can log in with a recovery code.</p> </div> } else if (Model.RecoveryCodesLeft == 1) { <div class="alert alert-danger"> <strong>You have 1 recovery code left.</strong> <p>You can<a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p> </div> } else if (Model.RecoveryCodesLeft <= 3) { <div class="alert alert-warning"> <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong> <p>You should <a asp-page= "./GenerateRecoveryCodes" > generate a new set of recovery codes</a>.</p> </div> } if (Model.IsMachineRemembered) { <form method="post" style="display: inline-block"> <button type="submit" class="btn btn-primary">Forget this browser</button> </form> } <a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a> <a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a> } <h5>Authenticator app</h5> @if (!Model.HasAuthenticator) { <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a> } else { <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Setup authenticator app</a> <a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a> } } else { <div class="alert alert-danger"> <strong>Privacy and cookie policy have not been accepted.</strong> <p>You must accept the policy before you can enable two factor authentication.</p> </div> } } @section Scripts { <partial name="_ValidationScriptsPartial" /> }
Section references:
After you enable two factor authentication, the ShowRecoveryCodes page should be displayed. This page doesn't get scaffolded.
Add ShowRecoveryCodes.cshtml.
@page @model ShowRecoveryCodesModel @{ ViewData["Title"] = "Recovery codes"; ViewData["ActivePage"] = "TwoFactorAuthentication"; } <partial name="_StatusMessage" for="StatusMessage" /> <h4>@ViewData["Title"]</h4> <div class="alert alert-warning" role="alert"> <p> <span class="glyphicon glyphicon-warning-sign"></span> <strong>Put these codes in a safe place.</strong> </p> <p> If you lose your device and don't have the recovery codes you will lose access to your account. </p> </div> <div class="row"> <div class="col-md-12"> @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) { <code class="recovery-code">@Model.RecoveryCodes[row]</code><text> </text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br /> } </div> </div>
Section references:
Add ShowRecoveryCodes.cshtml.cs.
// 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.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal { /// <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 class ShowRecoveryCodesModel : PageModel { /// <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> [TempData] public string[] RecoveryCodes { 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> [TempData] public string StatusMessage { 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 IActionResult OnGet() { if (RecoveryCodes == null || RecoveryCodes.Length == 0) { return RedirectToPage("./TwoFactorAuthentication"); } return Page(); } } }
Section references:
Be sure to edit the namespace to match your project.
You may have noticed the Register.cshtml.cs > OnPost() attempts to send an email to confirm the new user's email address. This function is not configured but does not fail because it references the IEmailSender in Microsoft.AspNetCore.Identity.UI.Services. If you want to take full control of the Identity UI by removing .AddDefaultUI(UIFramework.Bootstrap4) from AddIdentity in Startup.cs you need to provide an IEmailSender interface and EmailSender class.
Add a Services folder to the project and add an EmailSender class to Services.
public interface IEmailSender { Task SendEmailAsync(string email, string subject, string message); } public class EmailSender : IEmailSender { public Task SendEmailAsync(string email, string subject, string message) { return Task.CompletedTask; } }
This just stubs out the EmailSender. The next article will cover implementing a functional email service.
Add services.AddSingleton<IEmailSender, EmailSender>(); to the bottom of ConfigureServices in Startup.cs then remove .AddDefaultUI(UIFramework.Bootstrap4) from services.AddIdentity. Replace the reference using Microsoft.AspNetCore.Identity.UI.Services with using YOURPROJECTNAME.Services in the ForgotPassword.cshtml.cs and Register.cshtml.cs pages in the Account folder and the Index.cshtml.cs page in the Account\Manage folder.
With the Default Identity UI completely removed you will find the Manage your account pages were dependent on a layout which was not scaffolded.
The GitHub source has an elaborate layout scheme which relies on its own master layout page. I believe to support IdentityHostingStartup independently. I am not using IdentityHostingStartup yet and don't want to maintain two layout schemes. Here is my solution for now.
Add a _ViewStart.cshtml to the Account\Manage folder.
@{ Layout = "_Layout.cshtml"; }
Edit _Layout.cshtml in the Account\Manage folder.
@{ //Layout = "/Areas/Identity/Pages/_Layout.cshtml"; Layout = "/Pages/Shared/_Layout.cshtml"; }
Section references:
Fix the paths for src and asp-fallback-src in _ValidationScriptsPartial.cshtml in the Identity\Pages folder by removing /Identity before lib. You might think I am nitpicking here, but I have seen a Googlebot 404 error on this.
Edit _ValidationScriptsPartial.cshtml in the Identity\Pages folder.
<environment include="Development"> <script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> </environment> <environment exclude="Development"> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js" asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator" crossorigin="anonymous" integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js" asp-fallback-src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive" crossorigin="anonymous" integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds"> </script> </environment>
Now you have a completely scaffolded Identity UI. Look for my next article which will demonstrate a SMTP EmailSender.
Article references:
Comment Count = 11
Posted 04/03/2019 16:13
Member Since 04/03/2019
Great article, registered to say thanks. I thought I was going crazy when the nav title changed when entering Identity Area Pages. Searched all code but it was like voodoo, search brought me here and I have my answer so thank you very much Ken, I have implemented pretty much all your ui edits and all working fine.
Ken Haggerty *
Posted 04/03/2019 16:33
Member Since 01/04/2019
You're welcome.
Nick Downes
Posted 04/15/2019 07:18
Member Since 04/15/2019
Ken, this article is one of the best I have ever seen. I too registered to say thanks and how well written your explanation is. I will be looking at the other articles you have written. Thank you so much. Everything worked perfectly first time which is pretty rare these days.
Ken Haggerty *
Posted 04/15/2019 09:47
Member Since 01/04/2019
Thank you for the kind words. As a registered member, you may download the Bootstrap Native Project which implements most of the articles. The download is listed under Manage > Assets.
Szoke Laszlo
Posted 05/30/2019 11:47
Member Since 05/30/2019
Hello Ken, thank you! (from Hungary)
Ken Haggerty *
Posted 05/30/2019 14:23
Member Since 01/04/2019
You're welcome.

Serguei Khous
Posted 05/31/2019 08:48
Member Since 05/31/2019
Many thanks, Ken! Great article, nicely done. I've found all answers to my questions. Will read all your articles, for sure.

solarseven
Posted 06/19/2019 12:40
Member Since 06/18/2019
Thanks for these crystal clear and clean guides. More people should write like you do. Pure awesomeness!

Esteban
Posted 07/16/2019 22:45
Member Since 07/16/2019
Hi Ken, I added successfully an scaffolded Identity UI to my Solution. I want to edit the titles to Spanish and also stylize them. With titles, I am referring to Manage your account, Change your account settings, Profile, Username, Email and Phone number. Is there any way to achieve this?
Ken Haggerty *
Posted 07/16/2019 23:49
Member Since 01/04/2019
Globalization and localization in ASP.NET Core
You will probably have to inspect/edit every page for English dialog. The main navbar text is in /Pages/Shared/_Layout.cshtml and the Manage account text is in _Layout.cshtml and _ManageNav.cshtml in the /Areas/Identity/Pages/Account/Manage/ folder.

Esteban
Posted 08/05/2019 22:07
Member Since 07/16/2019
Thanks Ken, I followed your instructions and I achieved what I wanted.
Logged in users receive web notifications.

TinoMc