Click here to Skip to main content
15,882,017 members
Articles / Web Development / ASP.NET / ASP.NET Core

ASP.NET CORE Token Authentication and Authorization using JWT (No Cookies) – Part 1

Rate me:
Please Sign up or sign in to vote.
4.88/5 (36 votes)
5 Jul 2019CPOL12 min read 219K   4.9K   79   57
This article explains how to implement Token Authentication and Authorization using JWT in ASP.NET CORE.
This article demonstrates how to implement Token Authentication and Authorization using JWT (JSON Web Token) in ASP.NET CORE. The approach used in this article does not use any client side cookies for Authentication and Authorization. Which means, Token is not stored in client browser, it’s completely handled from server side. Since this article is mainly focused on implementing ASP.NET CORE Authentication and Authorization, we will not be going deep into Token Configuration and Token Creation. Only a brief explanation is given for Token Configuration and Creation from the implementation point of view. There are many articles which explain it in detail. This article includes the complete code and a LoginDemo.sln project.

Contents

Introduction

Before going to the topic, let’s have a brief explanation of Authentication and Authorization.

Authentication: Grant access/permission for users to enter the application. It’s like giving access/permission for a person to enter a building.

Authorization: This comes after Authentication. Grant permission for users only to certain pages of the application. It’s like a person who has access/permission to enter a building which has 10 floors, can ONLY go to the 2nd or 4th floor.

JWT (JSON Web Token)

As it says, JWToken is a JSON format string value. JWToken is issued for each valid user (Authentication). Token is created only once during user login. User will use that token in all subsequent HTTP requests for Authorization until that user log out from the application.

JWToken Configuration in ASP.NET Core

We would not go into to each and every detail of JWToken configuration. There are lot of articles which explain that. Configure JWT using Microsoft.AspNetCore. Authentication.JwtBearer and Microsoft.IdentityModel.Tokens. This is done in Startup.cs ConfigurationServices() method.

You can see in the below code, there are two parts in token configuration, services.AddAuthentication() and AddJwtBearer().

services.AddAuthentication(): This section is to configure the Authentication Scheme or Mechanism we are going to use. Here, we tell ASP.NET Core to use JWT Bearer Token Authentication. This is very important as this is going to be used in Configure() method later.

AddJwtBearer(): In this section, we configure the Token with Secret Key, Expiration Date, Consumer, etc. Secret Key is to encrypt and decrypt the token. Same secret key should be used while creating the token which we will see in “Create Token” topic.

C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddSession(options => {
        options.IdleTimeout = TimeSpan.FromMinutes(60);
    });
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
    //Provide a secret key to Encrypt and Decrypt the Token
    var SecretKey = Encoding.ASCII.GetBytes
         ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv");
    //Configure JWT Token Authentication
    services.AddAuthentication(auth =>
    {
        auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(token =>
    {
        token.RequireHttpsMetadata = false;
        token.SaveToken = true;
        token.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            //Same Secret key will be used while creating the token
            IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
            ValidateIssuer = true,
            //Usually, this is your application base URL
            ValidIssuer = "http://localhost:45092/",
            ValidateAudience = true,
            //Here, we are creating and using JWT within the same application.
            //In this case, base URL is fine.
            //If the JWT is created using a web service, then this would be the consumer URL.
            ValidAudience = "http://localhost:45092/",
            RequireExpirationTime = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
    });
}    

User Model Class

We need a model class for user to login. Create a model class for User with user id, password and other credentials. Create a class User.cs under “Models” folder.

C#
public class User
{
    public string USERID { get; set; }
    public string PASSWORD { get; set; }
    public string FIRST_NAME { get; set; }
    public string LAST_NAME { get; set; }
    public string EMAILID { get; set; }
    public string PHONE { get; set; }
    public string ACCESS_LEVEL { get; set; }
    public string READ_ONLY { get; set; }
}    

Create Token

Step 1

Let’s create a class TokenProvider.cs which would create/generate token for the user. Token is created only once and used in all subsequent request until user logoff. Under root folder of the solution, create a class TokenProvider.cs.

Step 2

Before creating the Token, we need to get the UserID from the login page and check if the user is present in our database. For demo purposes, list of users are hard coded values stored in a list. In the real world, this would be from database or some data source. Let’s add a property (UserList) to TokenProvider.cs class. This property is our user data store which has few hardcoded values.

C#
//Using hard coded collection list as Data Store for demo purposes
//In reality, User data comes from Database or other Data Source.
private List UserList = new List
{
    new User { USERID = "jsmith@email.com", PASSWORD = "test", 
               EMAILID = "jsmith@email.com", FIRST_NAME = "John", 
               LAST_NAME = "Smith", PHONE = "356-735-2748", 
               ACCESS_LEVEL = "Director", READ_ONLY = "true" },
    new User { USERID = "srob@email.com", PASSWORD = "test", 
               FIRST_NAME = "Steve", LAST_NAME = "Rob", 
               EMAILID = "srob@email.com", PHONE = "567-479-8537", 
               ACCESS_LEVEL = "Supervisor", READ_ONLY = "false" },
    new User { USERID = "dwill@email.com", PASSWORD = "test", 
               FIRST_NAME = "DJ", LAST_NAME = "Will", 
               EMAILID = "dwill@email.com", PHONE = "599-306-6010", 
               ACCESS_LEVEL = "Analyst", READ_ONLY = "false" },
    new User { USERID = "JBlack@email.com", PASSWORD = "test", 
               FIRST_NAME = "Joe", LAST_NAME = "Black", 
               EMAILID = "JBlack@email.com", PHONE = "764-460-8610", 
               ACCESS_LEVEL = "Analyst", READ_ONLY = "true" }
};    

Step 3

We need to set user permission for the application in the token (Authorization). In the token, we need to tell what level of permission user can have. User permissions are created as Claims. While creating token, we are going to set user permission in Claims Object collection and assign it to Token. These Claims values will be used to grant permission/authorize the user in controllers. In MVC controllers action methods, we would be using “ACCESS_LEVEL” and “READ_ONLY” claims to set user permission. For demo purposes, user claims are hard coded. Here, you can connect to your database and get user permission.

Let’s add a method (GetUserClaims()) to get user permission levels and build claims object collection in TokenProvider.cs class.

C#
//Using hard coded values in claims collection list as Data Store for demo. 
//In reality, User data comes from Database or other Data Source.
private IEnumerable GetUserClaims(User user)
{
    IEnumerable claims = new Claim[]
    {
        new Claim(ClaimTypes.Name, user.FIRST_NAME + " " + user.LAST_NAME),
        new Claim("USERID", user.USERID),
        new Claim("EMAILID", user.EMAILID),
        new Claim("PHONE", user.PHONE),
        new Claim("ACCESS_LEVEL", user.ACCESS_LEVEL.ToUpper()),
        new Claim("READ_ONLY", user.READ_ONLY.ToUpper())
    };
    return claims;
}    

Step 4

Now it’s time to create the token for the user. First, get the user id from login page and check if the user is in the UserList collection property declared above. If the user id is in the list, then we have a registered user. If not, then authentication fails. Do not issue the token.

Second, get the password from login page and check if the password matches with the password in the UserList. If yes, then create a token for user. If not, authentication fails and token is not created/issued.

To create JWToken, we would be using two namespaces, System.IdentityModel.Tokens.Jwt and Microsoft.IdentityModel.Tokens. Let’s create a token using JwtSecurityToken() class (Here, I am not covering the details of token creation. There are lot of articles which explain JWT token creation). While creating token, user claims values are loaded within the token “claims” property. We are calling the above function GetUserClaims() which loads claims for the User. Token is created in LoginUser() method which takes UserID and Password as input.

Let’s create a function LoginUser() which takes UserID and Password as input parameters in TokenProvider.cs.

C#
public string LoginUser(string UserID, string Password)
{
    //Get user details for the user who is trying to login
    var user = UserList.SingleOrDefault(x => x.USERID == UserID);
    
    //Authenticate User, Check if it’s a registered user in Database
    if (user == null)
        return null;
        
    //If it's registered user, check user password stored in Database 
    //For demo, password is not hashed. Simple string comparison 
    //In real, password would be hashed and stored in DB. Before comparing, hash the password
    if (Password == user.PASSWORD)
    {        
        //Authentication successful, Issue Token with user credentials
        //Provide the security key which was given in the JWToken configuration in Startup.cs
        var key = Encoding.ASCII.GetBytes
                  ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv"); 
        //Generate Token for user 
        var JWToken = new JwtSecurityToken(
            issuer: "http://localhost:45092/",
            audience: "http://localhost:45092/",
            claims: GetUserClaims(user),
            notBefore: new DateTimeOffset(DateTime.Now).DateTime,
            expires: new DateTimeOffset(DateTime.Now.AddDays(1)).DateTime,
            //Using HS256 Algorithm to encrypt Token
            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), 
                                SecurityAlgorithms.HmacSha256Signature)
        );
        var token = new JwtSecurityTokenHandler().WriteToken(JWToken);
        return token;
    }
    else
    {
        return null;
    }
}    

Few points to consider...

While creating token, we need to provide the same security key which is configured in Startup.cs for JWToken configuration.

C#
var key = Encoding.ASCII.GetBytes
          ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv");

issuer” and “audience” should be the same value which is configured in Startup.cs in ConfigureServices() method.

Finally, the TokenProvider.cs class looks like this:

C#
using LoginDemo.Models;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace LoginDemo
{
    public class TokenProvider
    {
        public string LoginUser(string UserID, string Password)
        {
            //Get user details for the user who is trying to login
            var user = UserList.SingleOrDefault(x => x.USERID == UserID);
            
            //Authenticate User, Check if it’s a registered user in Database 
            if (user == null)
                return null;
                
            //If it is registered user, check user password stored in Database
            //For demo, password is not hashed. It is just a string comparision 
            //In reality, password would be hashed and stored in Database. 
            //Before comparing, hash the password again.
            if (Password == user.PASSWORD)
            {
                //Authentication successful, Issue Token with user credentials 
                //Provide the security key which is given in 
                //Startup.cs ConfigureServices() method 
                var key = Encoding.ASCII.GetBytes
                ("YourKey-2374-OFFKDI940NG7:56753253-tyuw-5769-0921-kfirox29zoxv"); 
                //Generate Token for user 
                var JWToken = new JwtSecurityToken( 
                    issuer: "http://localhost:45092/",
                    audience: "http://localhost:45092/",
                    claims: GetUserClaims(user),
                    notBefore: new DateTimeOffset(DateTime.Now).DateTime,
                    expires: new DateTimeOffset(DateTime.Now.AddDays(1)).DateTime,
                    //Using HS256 Algorithm to encrypt Token  
                    signingCredentials: new SigningCredentials
                    (new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                );
                var token = new JwtSecurityTokenHandler().WriteToken(JWToken);
                return token;
            }
            else
            {
                return null;
            }
        }
        
        //Using hard coded collection list as Data Store for demo. 
        //In reality, User details would come from Database.
        private List UserList = new List
        {
            new User { USERID = "jsmith@email.com", 
            PASSWORD = "test", EMAILID = "jsmith@email.com", 
            FIRST_NAME = "John", LAST_NAME = "Smith", 
            PHONE = "356-735-2748", ACCESS_LEVEL = "Director", 
            READ_ONLY = "true" },
            new User { USERID = "srob@email.com", PASSWORD = "test", 
            FIRST_NAME = "Steve", LAST_NAME = "Rob", 
            EMAILID = "srob@email.com", PHONE = "567-479-8537", 
            ACCESS_LEVEL = "Supervisor", READ_ONLY = "false" },
            new User { USERID = "dwill@email.com", PASSWORD = "test", 
            FIRST_NAME = "DJ", LAST_NAME = "Will", 
            EMAILID = "dwill@email.com", PHONE = "599-306-6010", 
            ACCESS_LEVEL = "Analyst", READ_ONLY = "false" },
            new User { USERID = "JBlack@email.com", PASSWORD = "test", 
            FIRST_NAME = "Joe", LAST_NAME = "Black", 
            EMAILID = "JBlack@email.com", PHONE = "764-460-8610", 
            ACCESS_LEVEL = "Analyst", READ_ONLY = "true" }
        };
        
        //Using hard coded collection list as Data Store for demo. 
        //In reality, User data comes from Database or other Data Source 
        private IEnumerable GetUserClaims(User user)
        {
            IEnumerable claims = new Claim[]
                    {
                    new Claim(ClaimTypes.Name, user.FIRST_NAME + " " + user.LAST_NAME),
                    new Claim("USERID", user.USERID),
                    new Claim("EMAILID", user.EMAILID),
                    new Claim("PHONE", user.PHONE),
                    new Claim("ACCESS_LEVEL", user.ACCESS_LEVEL.ToUpper()),
                    new Claim("READ_ONLY", user.READ_ONLY.ToUpper())
                    };
            return claims;
        }
    }
}    

Token Storage

Now that we have authenticated the user and issued the token for that user, we need to store this token somewhere until the user logs out from the application. This is required since the token needs to be passed in each and every subsequent HTTP request after successful login. As mentioned above, we are not going to use any client (browser) side cookies to store the token.

Rather, we would be storing the token in server side in a user SESSION. Create a SESSION variable and store the token in it. After successful login, for each subsequent request, we would get the token from the session variable and insert into incoming HTTP Request.

We would be doing this in HomeController action method below, gets the token from TokenProvider.cs, create a Session object “JWToken” and store the token.

In HomeController.cs, there is a “LoginUser” action method. From Index.cshtml, user would input User ID and Password and submit the page to “LoginUser” action method in the HomeController.cs. In ‘LoginUser” controller action method, we will be adding the token to session object name “JWToken”.

C#
HttpContext.Session.SetString("JWToken", userToken);    

Middleware

Here comes the crucial part of the whole implementation. This part is more of a concept and few lines of code. We are going to do two things here:

  1. Insert the token into HTTP Request
  2. Load user claims into HTTP Request

Let’s understand the concept first. Trying to keep it simple, please bear with me.

Authentication and Authorization is handled through HTTP Request, to do that:

  • Token should be part of HTTP Request and it should come from HTTP Request Header.
  • ClaimsPrinciple and ClaimsIdentity (HttpContext.User.Identity) object is created from current HTTP Context.
  • User Claims are read from HTTP Request header and loaded into HTTP Claims Identity object
  • In other words, Authorization is done through incoming HTTP Request, NOT directly reading from the Token.
  • By doing this, HTTP Request itself is Authorized for that user.

To achieve the above:

  • We need to insert the Token (which is stored in user session variable “JWToken”) into each incoming HTTP Request.
  • Read user claims values from Token and load it into HTTP Context Claims Principle object.
  • If token in not available in session variable “JWToken”, then HTTP Request header “Authorization” would be empty. In that case, Claims Principle for that user will not be set in HTTP Context. That would deny permission for the user.

The below picture gives an idea about how we are going to insert the Token to HTTP header and set the Claims Principle in HTTP Context.

Image 1

Custom Middleware app.Use()

The main idea to have this custom middleware to insert the token into incoming HTTP Request. Now we have logged in user Token stored in Session variable “JWToken”, We need to insert that token into all subsequent incoming HTTP Request. For this, we are going to write a few lines of code into ASP.NET Core Middleware. This is nothing but HTTP pipeline. Custom Middleware is added in Startup.cs Configure() method.

P.S.: Token is created only once during user login.

Middleware app.UseAuthentication()

Now we need to validate the token and load the claims to HTTP Request context. UseAuthentication() does this job for us. Before HTTP Request hits the MVC controller, UseAuthentication() does the following:

  • Decrypting and Validating the Token using the secret key provided in AddJwtBearer() configuration (under ConfigureServices() method in Startup.cs)
  • Setting the User object in HTTP Request Context
  • Finally, read the Claims values from Token and load it to HttpContext.User.Identity object

Custom Middleware Code

In Startup.cs, add the following code to Configure() method. Add the below code after app.UseCookiePolicy(). Here, the code execution sequence is important.

C#
app.UseSession();
//Add JWToken to all incoming HTTP Request Header
app.Use(async (context, next) =>
{
    var JWToken = context.Session.GetString("JWToken");
    if (!string.IsNullOrEmpty(JWToken))
    {
        context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
    }
    await next();
});
//Add JWToken Authentication service
app.UseAuthentication();

Let’s go through the code:

  • app.UseSession() is configuration for using Session objects.
  • To write custom middleware, use app.Use().
  • First, we need the Token before we insert it to HTTP Request. We have stored the token in Session. Get the Token from session variable “JWToken”.
    C#
    var JWToken = context.Session.GetString("JWToken");                
  • The next line checks if Token is available in Session. If not, user is not Authenticated. So user permission is denied.
  • If the Token is present in Session variable “JWToken”, then we have Authenticated user.
  • Now, we need to add the Token to the HTTP Request (Remember, User Identity is created through HTTP Request). The below code adds the token to all incoming HTTP Requests.
    C#
    context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
    
  • Note, we are adding the Token to a “Authorization” header of the HTTP Request. Yes, it’s important to add the token to “Authorization” header and the token should be concatenated with a keyword “Bearer ”.
  • The next line of code is app.UseAuthentication().
    1. This line of code will look for the Authentication mechanism configured in ConfigureServices() method. In our ConfigureService(), we have used “AddJwtBearer” configuration, which is part of Microsoft.AspNetCore.Authentication namespace.
    2. Inside AddJWtBeared(), we have our token configuration with secret key, expiration date, etc.
    3. When HTTP Request comes in, app.UseAuthentication() will look for “Authorization” header in the HTTP Request. It will read the value stored in “Authorization” header and pass it to Microsoft.AspNetCore.Authentication. Microsoft.AspNetCore.Authentication will evaluate and validate the token as per the configuration we have set for the token. This includes decrypting the token using the secret key we have given in the configuration and reading the claims from the token and loading the claims to HttpContext.User.Identity object. Here, HTTP Context itself is Authenticated and Authorized.
    4. This complete execution is valid only for one HTTP Request (that particular incoming request). We have to do this for all subsequent HTTP Request. That’s the reason we store the Token in session variable and assign the Token to HTTP RequestAuthorization” header for all subsequent incoming HTTP Request. All incoming HTTP Request and outgoing HTTP Response goes through the HTTP Pipeline in Startup.cs Configure() method.

Finally, Startup.cs Configure() method looks like this:

C#
// 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.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();
    app.UseCookiePolicy();

    //Add User session
    app.UseSession();

    //Add JWToken to all incoming HTTP Request Header
    app.Use(async (context, next) =>
    {
        var JWToken = context.Session.GetString("JWToken");
        if (!string.IsNullOrEmpty(JWToken))
        {
            context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
        }
        await next();
    });
    //Add JWToken Authentication service
    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Login Page (Index.cshtml)

Now let’s create a simple login page (Index.cshtml) with user id and password textbox. Add User.cs model to view page. Here you can see the IF condition User.Identity.IsAuthenticated which checks if the user is Authenticated or not. “User” object is part of System.Security.Claims which is set in HTTP Context by the middleware. If user is authenticated, we show the user name from the claims identity name property. If not, then we ask the user to login.

HTML
@model LoginDemo.Models.User
@{
    ViewData["Title"] = "Home Page";
}
<div style="padding-top:50px;"></div>
<div style="padding-top:50px;">
    @if (User.Identity.IsAuthenticated)
    {
        <div class="row">
            You are Logged in as
            <span style="font-size:large;color:forestgreen;">
            @User.Identity.Name</span>
        </div>
        <div class="row" style="padding-top:50px;">
            @Html.ActionLink("Log Off", "Logoff",
            "Home", null, new { @class = "btn btn-primary btn-lg rph-login-button" })
        </div>
    }
    else
    {
        <div class="row">
            <div class="col-lg-4 col-md-4 col-sm-4">
                <div>
                    @using (Html.BeginForm("LoginUser", "Home",
                    FormMethod.Post, new { role = "form" }))
                    {
                        <div>
                            @Html.AntiForgeryToken()
                            <div>
                                <label>User ID</label><br />
                            </div>
                            <div>
                                @Html.TextBoxFor(m => m.USERID,
                                new {@class = "form-control txtbox"})
                            </div>
                            <div style="padding-top:20px;"></div>
                            <div>
                                <label>Password</label><br />
                            </div>
                            <div>
                                @Html.PasswordFor(m => m.USERID,
                                new {@class = "form-control txtbox"})
                            </div>
                        </div>
                        <div class="padding-left:35%;width:40%;">
                            <div class="padding-top:20px;">
                                <input class="btn btn-primary
                                btn-lg rph-login-button"
                                type="submit" value="Login"/>
                            </div>
                        </div>
                    }
                </div>
            </div>
            <div class="col-lg-8 col-md-8 col-sm-8">
                <div style="padding-top:50px;">
                    <div><b>Please login with any of the below User ID,
                    Password is span style="font-size:large;color:forestgreen;"
                    >test</span> for all Users</b></div>
                    <div style="padding-top:10px;">
                        <ui style="list-style: none;">
                            <li>jsmith@email.com  -  Director, Read Only - true</li>
                            <li>srob@email.com  -  Supervisor, Read Only - false</li>
                            <li>dwill@email.com  -  Analyst, Read Only - false</li>
                            <li>JBlack@email.com  -  Analyst, Read Only - true</li>
                        </ui>
                    </div>
                </div>
            </div>
        </div>
    }
</div>

Home Controller

Let’s add two Action methods in HomeController.cs. One for Index (Login) page and the other one to submit the login page.

C#
public IActionResult Index()
{
    return View();
}

public IActionResult LoginUser(User user)
{
    TokenProvider _tokenProvider = new TokenProvider();
    //Authenticate user
    var userToken = _tokenProvider.LoginUser(user.USERID.Trim(), user.PASSWORD.Trim());
    if (userToken != null)
    {
        //Save token in session object
        HttpContext.Session.SetString("JWToken", userToken);
    }
    return Redirect("~/Home/Index");
}

Action method LoginUser(User user) takes the user id and password values from login page. The below line does the authentication by checking user id and password in data store.

C#
var userToken = _tokenProvider.LoginUser(user.USERID.Trim(), user.PASSWORD.Trim());

Next lines check if there is a token issued by TokenProvider(). If yes, then save the token in user Session variable “JWToken”.

C#
if (userToken != null)
{
    //Save token in session object
    HttpContext.Session.SetString("JWToken", userToken);
}

Then, redirect the page to Index.cshtml:

C#
return Redirect("~/Home/Index");

During the page redirection, we have already stored the token in session object. Now the page redirection goes through the HTTP pipeline in Startup.cs. Now the custom middleware will stop the HTTP Request and insert the token into HTTP Request header “Authorization”. Please refer to "Middleware" for more details.

If token in not available in session variable “JWToken”, then HTTP Request header “Authorization” would be empty. In that case, HTTP Context will not be set for that user. Redirection will ask the user to login.

Log Off

Let's log off the user. When there is no token, then HTTP Context cannot be set for the user. So, remove the token from session object. To remove the token from session, clear the session for the user and redirect to another controller action.

Add a controller action method Logoff(). Clear the session for the user and redirect to Index action method. It is important to redirect to another controller action method. Let's see why? Say, in Logoff() action method, we return a View() instead of Redirect(). In this case, view page will be rendered to the browser and still users can access that page, User.Identity.IsAuthenticated is still true. When ASP.NET executes controller action method, it's in the process of HTTP RESPONSE. Which means it had already passed through HTTP REQUEST. User Claims Principle is set in HTTP Request. By logging off the user, we need to clear the Claims Principle for that user as well. Clearing the session alone is not enough. So we need to go through the HTTP Pipeline again. Redirection to another controller goes through the HTTP Pipeline and it will look for the Token in session variable "JWToken". But we have cleared the session, token is not in session anymore. Without token, Claims Principle cannot be set in the HTTP Context. This will completely log out the user.

C#
public IActionResult Logoff()
{
    HttpContext.Session.Clear();
    return Redirect("~/Home/Index");
}

Controller Code

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using LoginDemo.Models;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;

namespace LoginDemo.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult LoginUser(User user)
        {
            TokenProvider _tokenProvider = new TokenProvider();
            var userToken = _tokenProvider.LoginUser(user.USERID.Trim(),
                            user.PASSWORD.Trim());
            if (userToken != null)
            {
                //Save token in session object
                HttpContext.Session.SetString("JWToken", userToken);
            }
            return Redirect("~/Home/Index");
        }

        public IActionResult Logoff()
        {
            HttpContext.Session.Clear();
            return Redirect("~/Home/Index");
        }
    }
}

Login Demo Project

Login Page

Image 2

Image 3

LoginDemo.sln

Image 4

Part 2

In Part 2, we will cover Authorization for users. We are going to see:

  1. How to give page level access to users
  2. How to create custom authorize filter attribute to restrict users on controller level and action method level
  3. Decorate controller action methods with custom authorize attributes
  4. Restrict users from directly accessing a page without login

Go to Part 2

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
.NET Solution Architect and Developer.

C#, .NET, .NET Core, JQuery, AngularJS, Angular, RestFul API Web Service, MSSQL Server

Comments and Discussions

 
QuestionTo anyone experiencing problems Pin
terraslate29-Dec-22 17:02
terraslate29-Dec-22 17:02 
QuestionThis solution is not working in core 3.1 Pin
Devang Jani2-May-21 21:36
Devang Jani2-May-21 21:36 
Questionapp.UseAuthentication(); not working in .net core 3.1 Pin
Devang Jani2-May-21 21:08
Devang Jani2-May-21 21:08 
QuestionRefresh Tokens Pin
Member 1491637218-Mar-21 6:34
Member 1491637218-Mar-21 6:34 
Questionlocalhost redirected you too many times. Error Pin
Member 104831275-Jan-21 5:21
professionalMember 104831275-Jan-21 5:21 
QuestionCross Domain - Token Sharing Pin
Member 976294210-Jul-20 7:13
Member 976294210-Jul-20 7:13 
AnswerRe: Cross Domain - Token Sharing Pin
Joseph Rozario17-Aug-20 4:21
Joseph Rozario17-Aug-20 4:21 
Questionstoring token in Session? Pin
Akghasemi30-Mar-20 0:19
Akghasemi30-Mar-20 0:19 
QuestionClaimsPrinciple and ClaimsIdentity (HttpContext.User) Not set Pin
craig-dastardly23-Mar-20 6:46
craig-dastardly23-Mar-20 6:46 
AnswerRe: ClaimsPrinciple and ClaimsIdentity (HttpContext.User) Not set Pin
craig-dastardly23-Mar-20 7:22
craig-dastardly23-Mar-20 7:22 
GeneralRe: ClaimsPrinciple and ClaimsIdentity (HttpContext.User) Not set Pin
Joseph Rozario24-Mar-20 14:41
Joseph Rozario24-Mar-20 14:41 
GeneralMy vote of 4 Pin
Federico Navarrete14-Mar-20 6:26
professionalFederico Navarrete14-Mar-20 6:26 
GeneralRe: My vote of 4 Pin
Joseph Rozario16-Mar-20 4:14
Joseph Rozario16-Mar-20 4:14 
Questioncontext session get token always return null Pin
Member 1471321510-Jan-20 22:23
Member 1471321510-Jan-20 22:23 
AnswerRe: context session get token always return null Pin
Joseph Rozario13-Jan-20 6:05
Joseph Rozario13-Jan-20 6:05 
GeneralRe: context session get token always return null Pin
Member 1471321513-Jan-20 17:08
Member 1471321513-Jan-20 17:08 
GeneralRe: context session get token always return null Pin
Joseph Rozario14-Jan-20 3:31
Joseph Rozario14-Jan-20 3:31 
QuestionRemember me? Pin
Member 1470406931-Dec-19 5:54
Member 1470406931-Dec-19 5:54 
AnswerRe: Remember me? Pin
Joseph Rozario6-Jan-20 7:32
Joseph Rozario6-Jan-20 7:32 
Questionstore token in session Pin
hahaurdead22-Nov-19 8:56
hahaurdead22-Nov-19 8:56 
AnswerRe: store token in session Pin
Joseph Rozario25-Nov-19 3:32
Joseph Rozario25-Nov-19 3:32 
GeneralRe: store token in session Pin
hahaurdead25-Nov-19 10:26
hahaurdead25-Nov-19 10:26 
GeneralRe: store token in session Pin
Joseph Rozario26-Nov-19 4:27
Joseph Rozario26-Nov-19 4:27 
GeneralRe: store token in session Pin
hahaurdead26-Nov-19 7:00
hahaurdead26-Nov-19 7:00 
GeneralRe: store token in session Pin
Joseph Rozario26-Nov-19 9:51
Joseph Rozario26-Nov-19 9:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.