Scaffold Identity in ASP.NET Core projects

By Rick Anderson

ASP.NET Core provides ASP.NET Core Identity as a Razor Class Library. Applications that include Identity can apply the scaffolder to selectively add the source code contained in the Identity Razor Class Library (RCL). You might want to generate source code so you can modify the code and change the behavior. For example, you could instruct the scaffolder to generate the code used in registration. Generated code takes precedence over the same code in the Identity RCL. To gain full control of the UI and not use the default RCL, see the section Create full Identity UI source.

Applications that do not include authentication can apply the scaffolder to add the RCL Identity package. You have the option of selecting Identity code to be generated.

Although the scaffolder generates most of the necessary code, you need to update your project to complete the process. This document explains the steps needed to complete an Identity scaffolding update.

We recommend using a source control system that shows file differences and allows you to back out of changes. Inspect the changes after running the Identity scaffolder.

Services are required when using Two Factor Authentication, Account confirmation and password recovery, and other security features with Identity. Services or service stubs aren't generated when scaffolding Identity. Services to enable these features must be added manually. For example, see Require Email Confirmation.

When scaffolding Identity with a new data context into a project with existing individual accounts:

  • In Startup.ConfigureServices, remove the calls to:
    • AddDbContext
    • AddDefaultIdentity

For example, AddDbContext and AddDefaultIdentity are commented out in the following code:

public void ConfigureServices(IServiceCollection services)
{
    //services.AddDbContext<ApplicationDbContext>(options =>
    //    options.UseSqlServer(
    //        Configuration.GetConnectionString("DefaultConnection")));
    //services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    //    .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddControllersWithViews();
    services.AddRazorPages();
}

The preceeding code comments out the code that is duplicated in Areas/Identity/IdentityHostingStartup.cs

Typically, apps that were created with individual accounts should not create a new data context.

Scaffold Identity into an empty project

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Update the Startup class with code similar to the following:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Scaffold Identity into a Razor project without existing authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Identity is configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Migrations, UseAuthentication, and layout

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Enable authentication

Update the Startup class with code similar to the following:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

Layout changes

Optional: Add the login partial (_LoginPartial) to the layout file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WebRP</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">WebRP</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <partial name="_LoginPartial" />
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - WebRP - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Scaffold Identity into a Razor project with authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page so your layout file isn't overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages or Blazor Server projects with existing Razor Pages infrastructure
      • ~/Views/Shared/_Layout.cshtml for MVC projects or Blazor Server projects with existing MVC infrastructure
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Some Identity options are configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Scaffold Identity into an MVC project without existing authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Optional: Add the login partial (_LoginPartial) to the Views/Shared/_Layout.cshtml file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WebRP</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">WebRP</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <partial name="_LoginPartial" />
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - WebRP - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>
  • Move the Pages/Shared/_LoginPartial.cshtml file to Views/Shared/_LoginPartial.cshtml

Identity is configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Update the Startup class with code similar to the following:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

Scaffold Identity into an MVC project with authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page so your layout file isn't overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages or Blazor Server projects with existing Razor Pages infrastructure
      • ~/Views/Shared/_Layout.cshtml for MVC projects or Blazor Server projects with existing MVC infrastructure
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Scaffold Identity into a Blazor Server project without existing authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Identity is configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Migrations

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Pass an XSRF token to the app

Tokens can be passed to components:

  • When authentication tokens are provisioned and saved to the authentication cookie, they can be passed to components.
  • Razor components can't use HttpContext directly, so there's no way to obtain an anti-request forgery (XSRF) token to POST to Identity's logout endpoint at /Identity/Account/Logout. An XSRF token can be passed to components.

For more information, see ASP.NET Core Blazor Server additional security scenarios.

In the Pages/_Host.cshtml file, establish the token after adding it to the InitialApplicationState and TokenProvider classes:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

...

var tokens = new InitialApplicationState
{
    ...

    XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
};

Update the App component (App.razor) to assign the InitialState.XsrfToken:

@inject TokenProvider TokenProvider

...

TokenProvider.XsrfToken = InitialState.XsrfToken;

The TokenProvider service demonstrated in the topic is used in the LoginDisplay component in the following Layout and authentication flow changes section.

Enable authentication

In the Startup class:

  • Confirm that Razor Pages services are added in Startup.ConfigureServices.
  • If using the TokenProvider, register the service.
  • Call UseDatabaseErrorPage on the application builder in Startup.Configure for the Development environment.
  • Call UseAuthentication and UseAuthorization after UseRouting.
  • Add an endpoint for Razor Pages.
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    services.AddScoped<TokenProvider>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

Layout and authentication flow changes

Add a RedirectToLogin component (RedirectToLogin.razor) to the app's Shared folder in the project root:

@inject NavigationManager Navigation
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo("Identity/Account/Login?returnUrl=" +
            Uri.EscapeDataString(Navigation.Uri), true);
    }
}

Add a LoginDisplay component (LoginDisplay.razor) to the app's Shared folder. The TokenProvider service provides the XSRF token for the HTML form that POSTs to Identity's logout endpoint:

@using Microsoft.AspNetCore.Components.Authorization
@inject NavigationManager Navigation
@inject TokenProvider TokenProvider

<AuthorizeView>
    <Authorized>
        <a href="Identity/Account/Manage/Index">
            Hello, @context.User.Identity.Name!
        </a>
        <form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
            <button class="nav-link btn btn-link" type="submit">Logout</button>
            <input name="__RequestVerificationToken" type="hidden" 
                value="@TokenProvider.XsrfToken">
        </form>
    </Authorized>
    <NotAuthorized>
        <a href="Identity/Account/Register">Register</a>
        <a href="Identity/Account/Login">Login</a>
    </NotAuthorized>
</AuthorizeView>

In the MainLayout component (Shared/MainLayout.razor), add the LoginDisplay component to the top-row <div> element's content:

<div class="top-row px-4 auth">
    <LoginDisplay />
    <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>

Style authentication endpoints

Because Blazor Server uses Razor Pages Identity pages, the styling of the UI changes when a visitor navigates between Identity pages and components. You have two options to address the incongruous styles:

Build Identity components

An approach to using components for Identity instead of pages is to build Identity components. Because SignInManager and UserManager aren't supported in Razor components, use API endpoints in the Blazor Server app to process user account actions.

Use a custom layout with Blazor app styles

The Identity pages layout and styles can be modified to produce pages that use the default Blazor theme.

Note

The example in this section is merely a starting point for customization. Additional work is likely required for the best user experience.

Create a new NavMenu_IdentityLayout component (Shared/NavMenu_IdentityLayout.razor). For the markup and code of the component, use the same content of the app's NavMenu component (Shared/NavMenu.razor). Strip out any NavLinks to components that can't be reached anonymously because automatic redirects in the RedirectToLogin component fail for components requiring authentication or authorization.

In the Pages/Shared/Layout.cshtml file, make the following changes:

  • Add Razor directives to the top of the file to use Tag Helpers and the app's components in the Shared folder:

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @using {APPLICATION ASSEMBLY}.Shared
    

    Replace {APPLICATION ASSEMBLY} with the app's assembly name.

  • Add a <base> tag and Blazor stylesheet <link> to the <head> content:

    <base href="~/" />
    <link rel="stylesheet" href="~/css/site.css" />
    
  • Change the content of the <body> tag to the following:

    <div class="sidebar" style="float:left">
        <component type="typeof(NavMenu_IdentityLayout)" 
            render-mode="ServerPrerendered" />
    </div>
    
    <div class="main" style="padding-left:250px">
        <div class="top-row px-4">
            @{
                var result = Engine.FindView(ViewContext, "_LoginPartial", 
                    isMainPage: false);
            }
            @if (result.Success)
            {
                await Html.RenderPartialAsync("_LoginPartial");
            }
            else
            {
                throw new InvalidOperationException("The default Identity UI " +
                    "layout requires a partial view '_LoginPartial'.");
            }
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>
    
        <div class="content px-4">
            @RenderBody()
        </div>
    </div>
    
    <script src="~/Identity/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/Identity/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
    <script src="_framework/blazor.server.js"></script>
    

Scaffold Identity into a Blazor Server project with authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page so your layout file isn't overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages or Blazor Server projects with existing Razor Pages infrastructure
      • ~/Views/Shared/_Layout.cshtml for MVC projects or Blazor Server projects with existing MVC infrastructure
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Some Identity options are configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Create full Identity UI source

To maintain full control of the Identity UI, run the Identity scaffolder and select Override all files.

The following highlighted code shows the changes to replace the default Identity UI with Identity in an ASP.NET Core 2.1 web app. You might want to do this to have full control of the Identity UI.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<IdentityUser, IdentityRole>()
        // services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
        .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";
    });

    // using Microsoft.AspNetCore.Identity.UI.Services;
    services.AddSingleton<IEmailSender, EmailSender>();
}

The default Identity is replaced in the following code:

services.AddIdentity<IdentityUser, IdentityRole>()
    // services.AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

The following code sets the LoginPath, LogoutPath, and AccessDeniedPath:

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = $"/Identity/Account/Login";
    options.LogoutPath = $"/Identity/Account/Logout";
    options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});

Register an IEmailSender implementation, for example:

// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
public class EmailSender : IEmailSender
{
    public Task SendEmailAsync(string email, string subject, string message)
    {
        return Task.CompletedTask;
    }
}

Password configuration

If PasswordOptions are configured in Startup.ConfigureServices, [StringLength] attribute configuration might be required for the Password property in scaffolded Identity pages. InputModel Password properties are found in the following files:

  • Areas/Identity/Pages/Account/Register.cshtml.cs
  • Areas/Identity/Pages/Account/ResetPassword.cshtml.cs

Disable a page

This sections show how to disable the register page but the approach can be used to disable any page.

To disable user registration:

  • Scaffold Identity. Include Account.Register, Account.Login, and Account.RegisterConfirmation. For example:

    dotnet aspnet-codegenerator identity -dc RPauth.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.RegisterConfirmation"
    
  • Update Areas/Identity/Pages/Account/Register.cshtml.cs so users can't register from this endpoint:

    public class RegisterModel : PageModel
    {
        public IActionResult OnGet()
        {
            return RedirectToPage("Login");
        }
    
        public IActionResult OnPost()
        {
            return RedirectToPage("Login");
        }
    }
    
  • Update Areas/Identity/Pages/Account/Register.cshtml to be consistent with the preceding changes:

    @page
    @model RegisterModel
    @{
        ViewData["Title"] = "Go to Login";
    }
    
    <h1>@ViewData["Title"]</h1>
    
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
    
  • Comment out or remove the registration link from Areas/Identity/Pages/Account/Login.cshtml

    @*
    <p>
        <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
    </p>
    *@
    
  • Update the Areas/Identity/Pages/Account/RegisterConfirmation page.

    • Remove the code and links from the cshtml file.
    • Remove the confirmation code from the PageModel:
    [AllowAnonymous]
      public class RegisterConfirmationModel : PageModel
      {
          public IActionResult OnGet()
          {  
              return Page();
          }
      }
    

Use another app to add users

Provide a mechanism to add users outside the web app. Options to add users include:

  • A dedicated admin web app.
  • A console app.

The following code outlines one approach to adding users:

  • A list of users is read into memory.
  • A strong unique password is generated for each user.
  • The user is added to the Identity database.
  • The user is notified and told to change the password.
public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetRequiredService<AppDbCntx>();
                context.Database.Migrate();

                var config = host.Services.GetRequiredService<IConfiguration>();
                var userList = config.GetSection("userList").Get<List<string>>();

                SeedData.Initialize(services, userList).Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred adding users.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

The following code outlines adding a user:


public static async Task Initialize(IServiceProvider serviceProvider,
                                    List<string> userList)
{
    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    foreach (var userName in userList)
    {
        var userPassword = GenerateSecurePassword();
        var userId = await EnsureUser(userManager, userName, userPassword);

        NotifyUser(userName, userPassword);
    }
}

private static async Task<string> EnsureUser(UserManager<IdentityUser> userManager,
                                             string userName, string userPassword)
{
    var user = await userManager.FindByNameAsync(userName);

    if (user == null)
    {
        user = new IdentityUser(userName)
        {
            EmailConfirmed = true
        };
        await userManager.CreateAsync(user, userPassword);
    }

    return user.Id;
}

A similar approach can be followed for production scenarios.

Prevent publish of static Identity assets

To prevent publishing static Identity assets to the web root, see Introduction to Identity on ASP.NET Core.

Additional resources

ASP.NET Core 2.1 and later provides ASP.NET Core Identity as a Razor Class Library. Applications that include Identity can apply the scaffolder to selectively add the source code contained in the Identity Razor Class Library (RCL). You might want to generate source code so you can modify the code and change the behavior. For example, you could instruct the scaffolder to generate the code used in registration. Generated code takes precedence over the same code in the Identity RCL. To gain full control of the UI and not use the default RCL, see the section Create full identity UI source.

Applications that do not include authentication can apply the scaffolder to add the RCL Identity package. You have the option of selecting Identity code to be generated.

Although the scaffolder generates most of the necessary code, you'll have to update your project to complete the process. This document explains the steps needed to complete an Identity scaffolding update.

When the Identity scaffolder is run, a ScaffoldingReadme.txt file is created in the project directory. The ScaffoldingReadme.txt file contains general instructions on what's needed to complete the Identity scaffolding update. This document contains more complete instructions than the ScaffoldingReadme.txt file.

We recommend using a source control system that shows file differences and allows you to back out of changes. Inspect the changes after running the Identity scaffolder.

Note

Services are required when using Two Factor Authentication, Account confirmation and password recovery, and other security features with Identity. Services or service stubs aren't generated when scaffolding Identity. Services to enable these features must be added manually. For example, see Require Email Confirmation.

Scaffold Identity into an empty project

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Add the following highlighted calls to the Startup class:

public class Startup
{        
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();
        app.UseMvc();
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Scaffold Identity into a Razor project without existing authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Identity is configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Migrations, UseAuthentication, and layout

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Enable authentication

In the Configure method of the Startup class, call UseAuthentication after UseStaticFiles:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();

        app.UseMvc();
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

Layout changes

Optional: Add the login partial (_LoginPartial) to the layout file:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorNoAuth8</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">RazorNoAuth8</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Contact">Contact</a></li>
                </ul>
                <partial name="_LoginPartial" />
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - RazorNoAuth8</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

Scaffold Identity into a Razor project with authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page so your layout file isn't overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages or Blazor Server projects with existing Razor Pages infrastructure
      • ~/Views/Shared/_Layout.cshtml for MVC projects or Blazor Server projects with existing MVC infrastructure
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Some Identity options are configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

Scaffold Identity into an MVC project without existing authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add New Scaffolded Item dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
      • Blazor Server apps created from the Blazor Server template (blazorserver) aren't configured for Razor Pages or MVC by default. Leave the layout page entry blank.
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
  • Select Add.

Optional: Add the login partial (_LoginPartial) to the Views/Shared/_Layout.cshtml file:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MvcNoAuth3</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcNoAuth3</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>
                <partial name="_LoginPartial" />
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - MvcNoAuth3</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>
  • Move the Pages/Shared/_LoginPartial.cshtml file to Views/Shared/_LoginPartial.cshtml

Identity is configured in Areas/Identity/IdentityHostingStartup.cs. For more information, see IHostingStartup.

The generated Identity database code requires Entity Framework Core Migrations. Create a migration and update the database. For example, run the following commands:

In the Visual Studio Package Manager Console:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Add-Migration CreateIdentitySchema
Update-Database

The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema" describes the migration.

Call UseAuthentication after UseStaticFiles:

public class Startup
{

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();
        app.UseMvcWithDefaultRoute();
    }
}

UseHsts is recommended but not required. For more information, see HTTP Strict Transport Security Protocol.

Scaffold Identity into an MVC project with authorization

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page so your layout file isn't overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages or Blazor Server projects with existing Razor Pages infrastructure
      • ~/Views/Shared/_Layout.cshtml for MVC projects or Blazor Server projects with existing MVC infrastructure
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Run the Identity scaffolder:

  • From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • From the left pane of the Add Scaffold dialog, select Identity > Add.
  • In the Add Identity dialog, select the options you want.
    • Select your existing layout page, or your layout file will be overwritten with incorrect markup. When an existing _Layout.cshtml file is selected, it is not overwritten. For example:
      • ~/Pages/Shared/_Layout.cshtml for Razor Pages
      • ~/Views/Shared/_Layout.cshtml for MVC projects
  • To use your existing data context, select at least one file to override. You must select at least one file to add your data context.
    • Select your data context class.
    • Select Add.
  • To create a new user context and possibly create a custom user class for Identity:
    • Select the + button to create a new Data context class. Accept the default value or specify a class (for example, MyApplication.Data.ApplicationDbContext).
    • Select Add.

Note: If you're creating a new user context, you don't have to select a file to override.

Delete the Pages/Shared folder and the files in that folder.

Create full Identity UI source

To maintain full control of the Identity UI, run the Identity scaffolder and select Override all files.

The following highlighted code shows the changes to replace the default Identity UI with Identity in an ASP.NET Core 2.1 web app. You might want to do this to have full control of the Identity UI.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<IdentityUser, IdentityRole>()
        // services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
        .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";
    });

    // using Microsoft.AspNetCore.Identity.UI.Services;
    services.AddSingleton<IEmailSender, EmailSender>();
}

The default Identity is replaced in the following code:

services.AddIdentity<IdentityUser, IdentityRole>()
    // services.AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

The following code sets the LoginPath, LogoutPath, and AccessDeniedPath:

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = $"/Identity/Account/Login";
    options.LogoutPath = $"/Identity/Account/Logout";
    options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});

Register an IEmailSender implementation, for example:

// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
public class EmailSender : IEmailSender
{
    public Task SendEmailAsync(string email, string subject, string message)
    {
        return Task.CompletedTask;
    }
}

Password configuration

If PasswordOptions are configured in Startup.ConfigureServices, [StringLength] attribute configuration might be required for the Password property in scaffolded Identity pages. InputModel Password properties are found in the following files:

  • Areas/Identity/Pages/Account/Register.cshtml.cs
  • Areas/Identity/Pages/Account/ResetPassword.cshtml.cs

Disable register page

To disable user registration:

  • Scaffold Identity. Include Account.Register, Account.Login, and Account.RegisterConfirmation. For example:

    dotnet aspnet-codegenerator identity -dc RPauth.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.RegisterConfirmation"
    
  • Update Areas/Identity/Pages/Account/Register.cshtml.cs so users can't register from this endpoint:

    public class RegisterModel : PageModel
    {
        public IActionResult OnGet()
        {
            return RedirectToPage("Login");
        }
    
        public IActionResult OnPost()
        {
            return RedirectToPage("Login");
        }
    }
    
  • Update Areas/Identity/Pages/Account/Register.cshtml to be consistent with the preceding changes:

    @page
    @model RegisterModel
    @{
        ViewData["Title"] = "Go to Login";
    }
    
    <h1>@ViewData["Title"]</h1>
    
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
    
  • Comment out or remove the registration link from Areas/Identity/Pages/Account/Login.cshtml

@*
<p>
    <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
*@
  • Update the Areas/Identity/Pages/Account/RegisterConfirmation page.

    • Remove the code and links from the cshtml file.
    • Remove the confirmation code from the PageModel:
    [AllowAnonymous]
      public class RegisterConfirmationModel : PageModel
      {
          public IActionResult OnGet()
          {  
              return Page();
          }
      }
    

Use another app to add users

Provide a mechanism to add users outside the web app. Options to add users include:

  • A dedicated admin web app.
  • A console app.

The following code outlines one approach to adding users:

  • A list of users is read into memory.
  • A strong unique password is generated for each user.
  • The user is added to the Identity database.
  • The user is notified and told to change the password.
public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetRequiredService<AppDbCntx>();
                context.Database.Migrate();

                var config = host.Services.GetRequiredService<IConfiguration>();
                var userList = config.GetSection("userList").Get<List<string>>();

                SeedData.Initialize(services, userList).Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred adding users.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

The following code outlines adding a user:


public static async Task Initialize(IServiceProvider serviceProvider,
                                    List<string> userList)
{
    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    foreach (var userName in userList)
    {
        var userPassword = GenerateSecurePassword();
        var userId = await EnsureUser(userManager, userName, userPassword);

        NotifyUser(userName, userPassword);
    }
}

private static async Task<string> EnsureUser(UserManager<IdentityUser> userManager,
                                             string userName, string userPassword)
{
    var user = await userManager.FindByNameAsync(userName);

    if (user == null)
    {
        user = new IdentityUser(userName)
        {
            EmailConfirmed = true
        };
        await userManager.CreateAsync(user, userPassword);
    }

    return user.Id;
}

A similar approach can be followed for production scenarios.

Additional resources