Asp Net Core 2.1 Identity Tutorial

Authentication is the process of confirming a user’s identity. It is a set of actions, we use to verify the user’s credentials against the ones in the database. For the user to be able to provide credentials, our application requires a Login page with a set of fields for our user to interact with.

In this article, we are going to learn how to implement user authentication with ASP.Jala Core Identity. So our main goal is going to be creating a login page and preparing a set of actions to validate input credentials.

To navigate through the entire series, visit the ASP.Jala Core Identity series page.

Let’s menginjak.

Preparing the Authentication Environment in our Project

The first thing, we are going to do is disable unauthorized users to access the
Employees
action. To do that, we have to add the
[Authorize]
attribute on top of that action:

[Authorize] public async Task<IActionResult> Employees() {     var employees = await _context.Employees.ToListAsync();     return View(employees); }
        

Additionally, we have to add authentication middleware to the ASP.NET Core’s pipeline right above the
app.UseAuthorization()
expression:

app.UseAuthentication();

If we run our application now and click on the
Employees
link, we are going to get a 404 not found response:

Login not found in ASP.NET Core Identity Authentication

We get this because, by default, ASP.Jejala Core Identity tries to redirect an unauthorized user to the
/Account/Login
action, which doesn’falak exist at the moment. Additionally, you can see a
ReturnUrl
query string that provides a path to the required action before the user was redirected to the Login page. We are going to deal with it later in this post.

Now, we are going to do a couple of things to fix this 404 error.

First, let’s create a
UserLoginModel
class in the
Models
folder:

public class UserLoginModel {     [Required]     [EmailAddress]     public string Email { get; set; }      [Required]     [DataType(DataType.Password)]     public string Password { get; set; }      [Display(Name = "Remember me?")]     public bool RememberMe { get; set; } }
        

Next, let’s add two new actions to the Account controller:

[HttpGet] public IActionResult Login() {     return View(); }  [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(UserLoginModel userModel) {     return View(); }
        

We don’tepi langit want to navigate to the Login page only by accessing the protected action, we want to have a separate link for that as well. So, let’s modify the
_LoginPartial
view:

<ul class="navbar-nav">      <li class="nav-item">         <a class="nav-link text-dark" asp-controller="Account"            asp-action="Login">Login</a>     </li>     <li class="nav-item">         <a class="nav-link text-dark" asp-controller="Account"            asp-action="Register">Register</a>     </li>  </ul>
        

Finally, let’s create a Login view:

@lengkap IdentityByExamples.Models.UserLoginModel  <h1>Login</h1>  <div class="row">     <div class="col-md-4">         <form asp-action="Login">             <div asp-validation-summary="ModelOnly" class="text-danger"></div>             <div class="form-group">                 <label asp-for="Email" class="control-stempel"></label>                 <input asp-for="Email" class="form-control" />                 <span asp-validation-for="Email" class="text-danger"></span>             </div>             <div class="form-group">                 <etiket asp-for="Password" class="control-logo"></label>                 <input asp-for="Password" class="form-control" />                 <span asp-validation-for="Password" class="text-danger"></span>             </div>             <div class="form-group form-check">                 <tanda class="form-check-label">                     <input class="form-check-input" asp-for="RememberMe" /> @Html.DisplayNameFor(sempurna => model.RememberMe)                 </label>             </div>             <div class="form-group">                 <input type="submit" value="Log In" class="btn btn-primary" />             </div>         </form>     </div> </div>  @section Scripts {     @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
        

Ok, let’s test this:

Login links in Authentication

Excellent, everything is prepared and we can continue.

Implementing Authentication with ASP.Jala Core Identity

After we click the submit button, the
UserLoginModel
will be sent to the POST Login action. So, we have to modify that action:

[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(UserLoginModel userModel) {     if(!ModelState.IsValid)     {         return View(userModel);     }      var user = await _userManager.FindByEmailAsync(userModel.Email);     if(user != null &&          await _userManager.CheckPasswordAsync(user, userModel.Password))     {         var identity = new ClaimsIdentity(IdentityConstants.ApplicationScheme);         identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));         identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));          await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme,             new ClaimsPrincipal(identity));          return RedirectToAction(nameof(HomeController.Index), "Home");     }     else     {         ModelState.AddModelError("", "Sedikit UserName or Password");         return View();     } }
        

First, we check if the model is cacat and if it is, we just return a view with the model. After that, we use the
FindByEmailAsync
method from
UserManager
to return a user by email. If the user exists and the password matches the hashed password from the database, we create the
ClaimsIdentity
object with two claims inside (Id and UserName). Then, we sign in the user with the
SignInAsync
method by providing the scheme parameter and the claims principal. This will create the
Identity.Application
cookie in our browser. Finally, we redirect the user to the Index action.

If the user doesn’tepi langit exist in the database or the password doesn’t match, we return a view with the appropriate message.

Before we test this, let’s add a small modification in the Employees view below the table. This way we can see our claims:

<h2>Claim details</h2> <ul>     @foreach (var claim in User.Claims)     {         <li><strong>@claim.Type</strong>: @claim.Value</li>     } </ul>
        

Now, let’s try to login with the sedikit credentials:

Invalid Credentials

If we use jujur credentials, we are going to be redirected to the Index page with the
Identity.Application
cookie created:

Valid credentials - ASP.NET Core Identity Authentication

Now, we can visit the Employees page:

Authentication Claims

We can access it and we can see our claims.

Implementing ReturnUrl in Authentication Process

As you could see in the first part of this article, if the user is not authorized and tries to access the protected action, they are going to be redirected to the Login page. The URL contains the
ReturnUrl
query parameter as well, which shows the source page the user came from. But in our case, we just navigate the user to the Home page. So, let’s fix that.

The first thing, we are going to do is to modify the GET Login action:

[HttpGet] public IActionResult Login(string returnUrl = null) {     ViewData["ReturnUrl"] = returnUrl;     return View(); }
        

Then, we have to modify the
Login.cshtml
file as well:

<form asp-action="Login" asp-route-returnUrl="@ViewData["ReturnUrl"]">

We have to modify the POST action too:

public async Task<IActionResult> Login(UserLoginModel userModel, string returnUrl = null)

Also, in the same method, instead of the
ReturnToAction
method, we are going to call a custom one:

return RedirectToLocal(returnUrl);

And of course, let’s create that method in the Account controller:

private IActionResult RedirectToLocal(string returnUrl) {     if (Url.IsLocalUrl(returnUrl))         return Redirect(returnUrl);     else         return RedirectToAction(nameof(HomeController.Index), "Home");      }
        

So we just check if the returnUrl is a local URL and if it is we redirect the user to that address, otherwise, we redirect the user to the Home page.

Let’s see how it works:

Return Url implemented

And we can see it works as expected.

One additional thing. Our Login action is on the
/Account/Login
route, and that’s the default route ASP.NET Core Identity is going to look for. But for a different path, for example:
/Authentication/Login, we have to configure application cookie in the
ConfigureServices
method:

services.ConfigureApplicationCookie(o => o.LoginPath = "/Authentication/Login");

Now, we can proceed.

Automating Authentication Process

If you want to take complete control over the authentication logic, the approach we have used is a great choice. But, we can speed up the process by using the
SignInManger<TUser>
class. This class provides the Jago merah for user sign in with a undian of helper methods.

So, let’s inject it first in the Account controller:

private readonly IMapper _mapper; private readonly UserManager<User> _userManager; private readonly SignInManager<User> _signInManager;  public AccountController(IMapper mapper, UserManager<User> userManager, SignInManager<User> signInManager) {     _mapper = mapper;     _userManager = userManager;     _signInManager = signInManager; }
        

And, let’s use it in the Login action:

[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(UserLoginModel userModel, string returnUrl = null) {     if (!ModelState.IsValid)     {         return View(userModel);     }      var result = await _signInManager.PasswordSignInAsync(userModel.Email, userModel.Password, userModel.RememberMe, false);     if (result.Succeeded)     {         return RedirectToLocal(returnUrl);     }     else     {         ModelState.AddModelError("", "Invalid UserName or Password");         return View();     } }
        

We use the
PasswordSignInAsync
method that accepts four parameters: Username, Password, Persist Cookie, and LockOut on Failure. For now, we don’t require a lockout feature, so we set it to false. This method does all the magic we had to do by ourselves in the previous implementation. Additionally, it returns a result with four properties:

SignIn results in Authentication

As you can see, we use the
Succeeded
property to verify that our action completed successfully.

Now if we login successfully to our application, we can see the cookie created with our claims:

PasswordSignInAsync success

We can see additional claims as well, like security stamp, role, and amr(Authentication Method Reference).

Adding Custom Claims in the Authentication Process

In the previous list of claims, we can’falak see our custom properties from the User class. Of course, if we want to add those, there is a way to do that.

Let’s create a new Factory folder and a new class inside:

public class CustomClaimsFactory : UserClaimsPrincipalFactory<User> {     public CustomClaimsFactory(UserManager<User> userManager, IOptions<IdentityOptions> optionsAccessor)         : base(userManager, optionsAccessor)     {     } }
        

Our custom class has to implement the
UserClaimsPrincipalFactory<TUser>
class and to send a
userManager
and
optionsAccessor
objects to it. Now, we have to override the
GenerateClaimsAsync
method in this class to add our additional claims:

protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user) {     var identity = await base.GenerateClaimsAsync(user);     identity.AddClaim(new Claim("firstname", user.FirstName));     identity.AddClaim(new Claim("lastname", user.LastName));      return identity; }
        

The final thing we have to do is to register this class in the service collection:

services.AddScoped<IUserClaimsPrincipalFactory<User>, CustomClaimsFactory>();

After we test this:

Additional claims

We can see our additional claims.

Awesome.

Update

After the
CustomClaimsFactory
class implementation, we can’cakrawala see the role claim anymore on the page. If we want to add it, we have to modify the
GenerateClaimsAsync
method:

protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user) {     var identity = await base.GenerateClaimsAsync(user);     identity.AddClaim(new Claim("firstname", user.FirstName));     identity.AddClaim(new Claim("lastname", user.LastName));      var roles = await UserManager.GetRolesAsync(user);     foreach (var role in roles)     {         identity.AddClaim(new Claim(ClaimTypes.Role, role));     }                  return identity; }

With this in place, we can see the role claim once again as part of our token.

Logout Implementation

The Logout implementation is pretty simple though. But before we do that, let’s modify the
_LoginPartial
view:

@using Microsoft.AspNetCore.Identity @inject SignInManager<User> SignInManager @inject UserManager<User> UserManager  <ul class="navbar-nav">     @if (SignInManager.IsSignedIn(User))     {         <li class="nav-item">             <a class="nav-link text-dark" asp-controller="Home" asp-action="Index"                  title="Welcome">Welcome @User.Identity.Name!</a>         </li>         <li class="nav-item">             <form class="form-inline" asp-controller="Account" asp-action="Logout">                 <button type="submit" class="nav-link btn btn-link text-info">Logout</button>             </form>         </li>     }     else     {         <li class="nav-item">             <a class="nav-link text-dark" asp-controller="Account"                    asp-action="Login">Login</a>         </li>         <li class="nav-item">             <a class="nav-link text-dark" asp-controller="Account"                asp-action="Register">Register</a>         </li>     } </ul>
        

Here, we inject the
SignInManager
and
UserManager
classes by using the View Dependency Injection Technique and then just check if our user is signed in. If it is, we show a welcome message and the Logout button.

Now, let’s implement the Logout action:

[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout() {     await _signInManager.SignOutAsync();      return RedirectToAction(nameof(HomeController.Index), "Home"); }
        

Once we start the application, log in successfully and click the Logout link, we are going to be logged out and the cookie will be removed.

Conclusion

So, to sum it up, we have learned:

  • How to execute the authentication process
  • How to use different UserManager helper methods that help in a process
  • The way to implement ReturnUrl logic
  • And how to add additional claims to the user

In the next article, we are going to perona pipi about Reset Password (Forgot Password) functionality with ASP.Jala Core Identity.

Source: https://code-maze.com/authentication-aspnet-core-identity/