15

ASP.NET Core MVC Login and Registration with Identity

 4 years ago
source link: http://www.codaffection.com/asp-net-core-article/asp-net-core-mvc-login-and-registration-with-identity/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

In this article, we discuss how to implement ASP.NET Core MVC user authentication and registration with Identity.UI.

Sub-topics discussed :

  • How to add ASP.NET Core Identity to an existing project.
  • Customize ASP.NET Core Identity .
  • Identity.UI Design Customization.
  • Next Step.

Background

ASP.NET Core MVC with user authentication can be easily accomplished using ASP.NET Core Identity UI. While creating the MVC project, you just need to select Authentication as Individual User Accounts.

BfiE73f.jpg!web

The rest will be handled by ASP.NET Core Identity UI. It already contains razor view pages and backend codes for an authentication system. But that’s not what we want in most of the cases.

So in this tutorial, let’s look how to customize the ASP.NET Core Identity.

Create an ASP.NET Core MVC Project

First of all, I will create a brand new .Net Core MVC application without any authentication selected. then ASP.NET Core Identity can be added to the project with the help of the Identity Scaffolding template.

In Visual Studio 2019, Go to File > New > Project (Ctrl + Shift + N). From New Project window, Select Asp.Net Core Web Application.

VZfY3uE.jpg!web

Once you provide the project name and location. A new window will be opened as follows, Select Web Application(Model-View-Controller), uncheck HTTPS Configuration and DO NOT select any authentication method. Above steps will create a brand new ASP.NET Core MVC project.

aAB3uiA.jpg!web

Add ASP.NET Core Identity to Existing Project

To add ASP.NET Core Identity to the existing project, right-click on the project. Add > New Scaffolded Item . from the opened window, select Identity from the left panel and click on Add. It’ll open up another window as shown below.

7Njmqeq.jpg!web

With this window, we are trying to override selected operations from Identity. Here I’ve selected Login and Register to demonstrate, to you how much extend you can customize this library. Default _Layout.cshtml is selected for the Identity pages. New class is mentioned for DbContext and User (rather than typing class name directly in text-field, use plus button on right). Finally, click on Add. then the new Identity Area will be added with selected pages and related files.

Now update Startup.cs file as bellow.

Copy to Clipboard
...

public void ConfigureServices(IServiceCollection services)
{
    ...
    //to be added
    services.AddRazorPages();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRouting();

    //to be added
    app.UseAuthentication();
    app.UseAuthorization();

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

ASP.NET Core Identity.UI uses Razor pages for UI. Hence in ConfigureServices, we’ve to call AddRazorPages from services. Identity Contains following Routes,

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Register
  • etc

To add these routes into the app, Inside Configure function, while calling UseEndpoints function we’ve to call MapRazorPages function. UseAuthentication is called to enable authentication. Now, if you navigate mentioned Identity URLs, you could see corresponding interface. we’ve not yet created Database for the app, so form submission won’t work yet.

Customize ASP.NET Core Identity

First of all, I want to add the first and last name of user in user model. that we can do inside our new model class file- /Areas/Identity/Data/ApplicationUser.cs.

Copy to Clipboard
public class ApplicationUser : IdentityUser
{
    [PersonalData]
    [Column(TypeName ="nvarchar(100)")]
    public string FirstName { get; set; }

    [PersonalData]
    [Column(TypeName = "nvarchar(100)")]
    public string LastName { get; set; }
}

As mentioned in /Areas/Identity/IdentityHostingStartup.cs file, this Identity will be using connection string – AuthDbContextConnection. which is saved in appSettings.json file. If you want to change SQL Server instance or DB. you can do that. I use a difference instance and DB.

Copy to Clipboard
"ConnectionStrings": {
    "AuthDbContextConnection": "Server=(local)\\sqlexpress;Database=MVCAuthDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  }

Now we can create the physical DB through DB Migration. From Solution Explorer, select the project, go to Tools>NuGet Package Manager > Package Manager Console. Then execute the following commands one by one.

Copy to Clipboard
Add-Migration "InitialCreate"
Update-Database

After successful DB Migration, Along with other Identity tables, inside AspNetUsers, you could see columns for First Name and Last Name.

Design Customization

First of to add visual flavors into the app, Font Awesome and Google Font-‘Roboto’ stylesheet reference are added to MVC default layout – Views/Shared/_Layout.cshml.

Copy to Clipboard
...
<head>
    ...
    <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
    <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'" rel="preload">
</head>
<div>
    <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-controller="Home" asp-action="Index">Authentication System</a>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
					//partial layout for logged in user.
                    <partial name="_LoginPartial" />
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
    ..

In this layout, you could see the partial render for _LoginPartial. it shows HTML elements for logged user with logout button. Currently inside the same file, it is showing Login and Register Link in header. we don’t have to show them in header. so update _LoginPartial as shown below.

Copy to Clipboard
@using Microsoft.AspNetCore.Identity
@using Project.Areas.Identity.Data

@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
    </li>
    <li class="nav-item">
        <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
            <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
</ul>

Now let’s add following css rules in global style sheet as follows – wwwroot/css/site.css.

Copy to Clipboard
...
body {
    font-family: 'Roboto', sans-serif;
}

/*for tab control*/
div.login-logout-tab div.card-header {
    padding: 0px 0px 12px 0px;
}

div.login-logout-tab ul.nav-tabs {
    margin: 0px 0px -12px 0px;
}

div.login-logout-tab li.nav-item {
    width: 50%;
}

div.login-logout-tab a.nav-link {
    font-size: 25px;
    color: #495057;
    text-align:center;
}

div.card-content{
    padding : 10px 20px;
}

/*login form*/
div.login-form-icon{
    text-align:center;
}

Now we are going to show login and registration form side by side in a tab control. For that we need a nested layout page for login and register page. So let’s create _AuthLayout.cshtml in /Areas/Identity/Pages. For that right-click on Pages folder, Add>NewItem. Select Razor Layout, name the file as _AuthLayout.cshtml. Replace the file as shown below.

Copy to Clipboard
@{
    Layout = "/Views/Shared/_Layout.cshtml";
}

<div class="row">
    <div class="col-md-6 offset-md-3">
        <div class="card login-logout-tab">
            <div class="card-header">
                <ul class="nav nav-tabs card-header-tabs">
                    <li class="nav-item">
                        <a class="nav-link" href='/Identity/Account/Login'>Sign In</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href='/Identity/Account/Register'>Sign Up</a>
                    </li>
                </ul>
            </div>
            <div class="card-content">
                <div class="col-md-12">
                    @RenderBody()
                </div>
            </div>
        </div>
    </div>
</div>

@section Scripts{
    @RenderSection("Scripts", required: false) <script>$(function () {
            var current = location.pathname;
            $('.nav-tabs li a').each(function () {
                var $this = $(this);
                if (current.indexOf($this.attr('href')) !== -1) {
                    $this.addClass('active');
                }
            })
        })</script> }

we want to show the tab control like this.

yyaQRnJ.jpg!web

First of all we’ve main layout(_Layout.cshtml) and then inside that the nested layout(_AuthLayout). In place of @RenderBody(), we’ve to show html from respective razor pages based on URLs. It might be either Login Or Registration page. Inside the script section, based on current URL, we added ‘active’ class to the respective tab header.

User Registration

Now let’s look what we’ve to do in user registration form, which is inside /Areas/Identity/Pages/Account as Register.cshtml . Its a Razor Page not MVC Razor View, there are few differences. It has a view file with extension .cshtml and for handling back-end part, it has a C# file with the extension .cshtml.cs (like we had in old .aspx web form page).

The back-end contains two main functions, OnGetAsync and OnPostAsync to handle GET and POST requests respectively. Inside the registration back-end, we could see InputModel for designing the Registration Form. We’ve to update that for adding controls for first-name and last-name in user registration form.

Copy to Clipboard
public class InputModel
{

    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    ...
}

To save these property values to correspond table – AspNetUsers. Update OnPostAsync,  save the first name and last name to ApplicationUser  instance before inserting into the table.

Copy to Clipboard
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
  ...
  var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email,
                        FirstName = Input.FirstName, LastName = Input.LastName };
  var result = await _userManager.CreateAsync(user, Input.Password);
  ...
}

Now let’s update the razor file as follows.

Copy to Clipboard
@page
@model RegisterModel

@{ ViewData["Title"] = "Register"; }

@{ Layout = "~/Areas/Identity/Pages/_AuthLayout.cshtml"; }

<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Input.FirstName"></label>
                <input asp-for="Input.FirstName" class="form-control" />
                <span asp-validation-for="Input.FirstName" class="text-danger"></span>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Input.LastName"></label>
                <input asp-for="Input.LastName" class="form-control" />
                <span asp-validation-for="Input.LastName" class="text-danger"></span>
            </div>
        </div>
    </div>
    <div class="form-group">
        <label asp-for="Input.Email"></label>
        <input asp-for="Input.Email" class="form-control" />
        <span asp-validation-for="Input.Email" class="text-danger"></span>
    </div>
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Input.Password"></label>
                <input asp-for="Input.Password" class="form-control" />
                <span asp-validation-for="Input.Password" class="text-danger"></span>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Input.ConfirmPassword"></label>
                <input asp-for="Input.ConfirmPassword" class="form-control" />
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
            </div>
        </div>
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
</form>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

I’ve set the layout to _AuthLayout.cshtml  and added form controls for FirstName and LastName. Now if you navigate ‘/Identity/Account/Register’,  you could see the following user registration form.

6viEry7.jpg!web

If you test the registration form you could see, by default Identity apply some validations to the form controls. you can override them as follows in /Areas/Identity/IdentityHostingStartup.cs file.

Copy to Clipboard
public class IdentityHostingStartup : IHostingStartup
{
    public void Configure(IWebHostBuilder builder)
    {
           ...

            services.AddDefaultIdentity<ApplicationUser>(options => {
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.SignIn.RequireConfirmedAccount = false;
            })
                .AddEntityFrameworkStores<AuthDbContext>();
        });
    }
}

By default, there should be at least one lowercase and uppercase character, we’ve disabled that here. The confirmation of email address before first login is also disabled.

Login Form

User authentication or login is handled inside – /Areas/Identity/Pages/Account/Login.cshtml. Now you can update the razor html as follows.

Copy to Clipboard
@page
@model LoginModel

@{ViewData["Title"] = "Login";}


@{
    Layout = "~/Areas/Identity/Pages/_AuthLayout.cshtml";
}
<div class="col-md-10 offset-1">
    <div class="login-form-icon">
        <i class="fas fa-user-circle fa-9x text-secondary"></i>
    </div>
    <form id="account" method="post">
        <div asp-validation-summary="All" class="text-danger"></div>
        <div class="form-group">
            <div class="input-group">
                <div class="input-group-prepend">
                    <div class="input-group-text">
                        <i class="fas fa-envelope"></i>
                    </div>
                </div>
                <input asp-for="Input.Email" class="form-control" placeholder="Email Address" />
            </div>
            <span asp-validation-for="Input.Email" class="text-danger"></span>
        </div>
        <div class="form-group">
            <div class="input-group">
                <div class="input-group-prepend">
                    <div class="input-group-text">
                        <i class="fas fa-lock"></i>
                    </div>
                </div>
                <input asp-for="Input.Password" class="form-control" placeholder="Password" />
            </div>
            <span asp-validation-for="Input.Password" class="text-danger"></span>
        </div>
        <div class="form-group">
            <div class="checkbox">
                <label asp-for="Input.RememberMe">
                    <input asp-for="Input.RememberMe" />
                    @Html.DisplayNameFor(m => m.Input.RememberMe)
                </label>
            </div>
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary btn-block">Log in</button>
        </div>
    </form>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Now the login form should look like this.

V3QZFf2.jpg!web

Now, this login form should work as we expect. Finally, if you want to redirect the user back to home from login or registration when he/ she is already logged in. you just need to add following if statement in login and register page, inside the function onGetAsync as shown below.

Copy to Clipboard
public async Task OnGetAsync(string returnUrl = null)
{
    if (User.Identity.IsAuthenticated)
    {
        Response.Redirect("/Home");
    }

    ...
}

Finally, you could protect controllers or actions from unauthorized request using Authorize attribute. If there is any unauthorized request made, user will be redirected to login page. I’ve done the same in HomeController.

file – Controllers/HomeController.cs

Copy to Clipboard
[Authorize]
public class HomeController : Controller
{ ... }

Next Step

Here we have tried to override and customize ASP.NET Core Identity library for basic authentication. There are lot of things to be implemented like

  • Email Confirmation
  • Forgot Password
  • Use of JWT Authentication
  • Incorporate with External Authentication like Facebook, Google, etc.

We’ll let you know once we post articles on these topics. If you have any query related to ASP.NET Core Identity let me know.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK