Monday, 26 April 2021

Cookie Authentication in ASP.NET Core

In this article, we will learn How to implement Cookie Authentication in ASP.NET Core without using Identity. I will try to cover every step so that beginners can easily understand.

This article is the part of ASP.NET Core learning series. If you are new to ASP.NET Core, then I will recommend you to go through the previous article of this series:
Authentication is the process of verifying the identity of the user.  In this article, we will authenticate the user by verifying the user’s credentials. I am using Visual Studio 2019 to demonstrate this example.

Let’s implement the Cookie Authentication in ASP.NET Core step by step:
Open the Visual Studio and click on Create a new Project.
Select ASP.NET Core Empty project and click on next.
Give a name to your Project, select the location for the project creation, and click on Next.
Select the Target Framework. I am using .Net Core 3.1 (LTS) and click on create.
A similar kind of project as shown in the below image will be created.
As we have created an empty project, let's open the Startup.cs file and add the highlighted code. In the ConfigureServices method, I have added the AddControllersWithViews service and added the UseEndpoints middleware in Configure method as shown in the below image.
Add a Controllers folder in the project in which we will create all the controllers.
Let’s add a HomeController by right-clicking on the Controller folder. Go to Add and click on Controller. Select the empty controller as shown in the below image.

Right-click on the project and click on manage NuGet Packages. Browse Bootstrap (only required for the UI Style) and click on Install.
In order to serve the static files like js, css, images, etc., we need to UseStaticFiles middleware in Configure method.
I have added to Action methods in HomeController i.e., Index and ConfidentialData.
So, on clicking on the Home and confidential Data link in the navigation below screen will be shown. 

Another controller i.e., AccountController with a Login page as shown in the below image.
In ConfigureServices method of Startup.cs, create an Authentication Middleware Services with the AddAuthentication and AddCookie method. Authentication scheme passed to AddAuthentication sets to the default authentication scheme for the app. CookieAuthenticationDefaults.AuthenticationScheme provides “Cookies” for the scheme. In AddCookie extension method, set the LoginPath property of CookieAuthenticationOptions to “/account/login”. CookieAuthenticationOptions class is used to configure the authentication provider options.
In Configure method of Startup.cs, call UseAuthentication and UseAuthorization method before calling the endpoints.
Now let’s add the Authorize attribute, on ConfidentialData action method. Now only authenticate methods can access that ActionMethod.
Let’s click on Confidential Data link in the navbar. It will redirect to “Account/Login” page as the user is not authenticated yet.
In AccountController, the Login action method receives the returnurl as a parameter to which the user needs to be redirected after the successful authentication. Below is the Login.cshtml code used in the example.

@model CookieAuthentication.Models.LoginModel

 

@{

    ViewData["Title"] = "Login";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

 

<h2>Login</h2>

 

<hr />

<div class="row">

    <div class="col-md-4">

        <form asp-action="Login">

            <div asp-validation-summary="ModelOnly" class="text-danger"></div>

            @if (!string.IsNullOrEmpty(ViewBag.Message))

            {

                <span class="text-danger">

                    @ViewBag.Message

                </span>

            }

            @Html.HiddenFor(x => x.ReturnUrl)

            <div class="form-group">

                <label asp-for="UserName" class="control-label"></label>

                <input asp-for="UserName" class="form-control" />

                <span asp-validation-for="UserName" class="text-danger"></span>

            </div>

            <div class="form-group">

                <label asp-for="Password" class="control-label"></label>

                <input asp-for="Password" class="form-control" />

                <span asp-validation-for="Password" class="text-danger"></span>

            </div>

            <div class="form-group">

                <div class="checkbox">

                    <label>

                        <input asp-for="RememberLogin" /> @Html.DisplayNameFor(model => model.RememberLogin)

                    </label>

                </div>

            </div>

            <div class="form-group">

                <input type="submit" value="Login" class="btn btn-default" />

            </div>

        </form>

    </div>

</div>

LoginModel.cs file:

public class LoginModel

{

        [Required]

        [Display(Name ="Username")]

        public string UserName { get; set; }

        [Required]

        [DataType(DataType.Password)]

        public string Password { get; set; }

        public bool RememberLogin { get; set; }

        public string ReturnUrl { get; set; }

 

}

On clicking on the login button, the Login Post action will be triggered. In Post action, we are verifying the username and password (In this example, I am using the hardcoded user details, but in actual you can verify the user details with an ORM like EntityFramework, Dapper, etc. If entered credentials are not valid then an Invalid credential message will be shown to the user. If credentials are correct create a ClaimsIdentity with the required Claims. Call the SignInAsync to sign in the user.

public class AccountController : Controller

{

        //Sample Users Data, it can be fetched with the use of any ORM

        public List<UserModel> users = null;

        public AccountController()

        {

            users = new List<UserModel>();

            users.Add(new UserModel() { UserId = 1, Username = "Anoop", Password = "123", Role = "Admin" });

            users.Add(new UserModel() { UserId = 2, Username = "Other", Password = "123", Role = "User" });

        }

 

        public IActionResult Login(string ReturnUrl = "/")

        {

            LoginModel objLoginModel = new LoginModel();

            objLoginModel.ReturnUrl = ReturnUrl;

            return View(objLoginModel);

        }

        [HttpPost]

        public async Task<IActionResult> Login(LoginModel objLoginModel)

        {

            if (ModelState.IsValid)

            {

                var user = users.Where(x => x.Username == objLoginModel.UserName && x.Password == objLoginModel.Password).FirstOrDefault();

                if (user == null)

                {

                    //Add logic here to display some message to user

                    ViewBag.Message = "Invalid Credential";

                    return View(objLoginModel);

                }

                else

                {

                    //A claim is a statement about a subject by an issuer and

                    //represent attributes of the subject that are useful in the context of authentication and authorization operations.

                    var claims = new List<Claim>() {

                    new Claim(ClaimTypes.NameIdentifier,Convert.ToString(user.UserId)),

                    new Claim(ClaimTypes.Name,user.Username),

                    new Claim(ClaimTypes.Role,user.Role),

                    new Claim("FavoriteDrink","Tea")

                    };

                    //Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme

                    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

                    //Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity

                    var principal = new ClaimsPrincipal(identity);

                    //SignInAsync is a Extension method for Sign in a principal for the specified scheme.

                    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,

                        principal, new AuthenticationProperties() { IsPersistent = objLoginModel.RememberLogin });

 

                    return LocalRedirect(objLoginModel.ReturnUrl);

                }

            }

            return View(objLoginModel);

        }

 

        public async Task<IActionResult> LogOut() {

            //SignOutAsync is Extension method for SignOut

            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            //Redirect to home page

            return LocalRedirect("/");

        }

}

Once the user is successfully logged in to the application, a cookie will be generated as shown in the below image. This encrypted cookie will be sent to the server in each request and validated on the server with its key.
In order to show the Claims information on Confidential Data view, code, as shown in the below, is used. In the below code, we are checking that the user must be Authenticated before looping through each Claims.

Logout link is shown to the user who is already signed in. On Clicking on the Logout link, we are calling the SignOutAsync method which signs out the user and deletes their cookie.

public async Task<IActionResult> LogOut() {

            //SignOutAsync is Extension method for SignOut

            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            //Redirect to home page

            return LocalRedirect("/");

}

Final Preview:

I hope this article helped you in implementing Cookie Authentication in ASP.

Thanks,

[Download Source code via Google Drive]

You can also get the code through the GitHub

0 comments:

Post a comment

Subscribe Now

Popular Posts

Contact us

Name

Email *

Message *

Subscribe us on YouTube

Like us on Facebook