JWT VS Session VS Cookie for ASP.NET Core Web Api

Preface

In this article, we will discuss the JWT VS Session. This problem is not too much to think about. When we see that comments and discussions are too heated, we spend a little time to research and summarize. By the way, this is the benefit of blogging. A blog may write with accumulated experience, or it may be learning to share, but it all runs away.However, you have more or better ideas when you read the article, and you can gain more by communicating back and forth. Why not?I hope this article can be confused or get more communication.We can directly ask the question: Is it better to use a JWT stored by the client than to maintain a Session on the server?(

Common points based on JWT and Ession authentication

Now that we're comparing JWT VS Session, we know why we need JWT and Session, and what are they all working together to solve?So let's start with a scenario where online shopping is no longer a common occurrence. When we add a commodity to the shopping cart, then jump to another commodity page and the previously selected commodity is still in the shopping cart, then we need to maintain the session, because HTTP is stateless, so JWT and Sassin have something in commonBoth exist for the purpose of persistent session maintenance. To overcome HTTP statelessness, how are JWT and Session handled respectively?

 

JWT VS Session Certification

Session: When a user logs in to the application system, the server creates a Session (also known as a session), and the SessionId is saved in the user's cookie. As long as the user is logged in, the SessionId in the cookie is sent to the server for each request, and the server saves it in theSessionId in memory is compared with SessionId in Cookie to authenticate the user and respond.

JWT: When a user logs in to the application system, the server creates a JWT and sends it to the client, which stores the JWT (typically in Local Storage) and also contains the JWT in each request header, Authorization For each request, the server verifies that the JWT is legal and directly in the service.End-to-end local authentication, such as issuers, acceptors, and so on, eliminates the need to make network requests or interact with the database, which may be faster than using Session, thereby increasing response performance and reducing server and database server load.

 

From the brief description of JWT authentication and Session authentication above, we know that the biggest difference between them is that Session is stored on the server side and JWT is stored on the client side.There are two kinds of server-side storage sessions, one is to store session identifiers in the database, the other is to store session in memory. I think most of the time it is based on memory to maintain session, but this will bring some problems. If there is a large amount of traffic on the system, that is, if there are a large number of users accessing the systemUse of memory-based maintenance sessions limits horizontal expansion at this time, but there is no such problem with Token-based authentication, and cookies generally only apply to single or subdomains. For cross-domain, if it is a third-party Cookie, the browser may disable cookies, so it is also restricted by the browser, but for TokenAuthentication is not a problem because it is saved in the request header.

 

If we move the session to the client, that is, using Token authentication, the session will be decoupled from the server and scalable horizontally without browser restrictions, but there will also be some problems. First, token transmission security. For token transmission security, we can use HTTPS Encrypted Channel Solution and Second, JWT is significantly larger than SessionId stored in Cookie s because JWT also contains user information, so to solve this problem, we try to ensure that JWT contains only the necessary information (in most cases only sub s and other important information), for sensitive information IThey should also be omitted to prevent XSS attacks.The core of JWT is to declare that JSON data is declared in JWT, that is, we can embed user information in JWT to reduce database load.So to sum up, the above JWT solves the problems or shortcomings of other sessions:

More flexibility

More secure

Reduce database round trips to achieve horizontal scalability.

Tamper-proof client declaration

Work better on mobile devices

For users blocking cookies

To sum up, it is obviously unreasonable to completely deny the benefits of JWT without forcing it to be invalid during its validity period. Of course, it is irrefutable that other types of authentication are entirely reasonable and legitimate without many of the above limitations, which requires a comprehensive trade-off rather than a one-person conclusion.So far, we have been discussing JWT VS Session certification, not JWT VS Cookie certification, but if we include cookies in the title, we just want to keep the learners from confusing, because JWT VS Cookie certification is wrong. Cookies are just a medium for storing and transporting information. We can only say that we canStore and transfer JWTs through cookies.Next we implement cookie storage and transfer of JWT tokens.

 

JWT AS Cookies Identity Claim

In Startup, we can add the following Cookie authentication middleware. It is necessary to understand some options for configuring cookies. By configuring these options, we can tell how Cookie authentication middleware behaves in browsers. Let's look at several options related to security.

           services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
           .AddCookie(options =>
           {
               options.LoginPath = "/Account/Login";
               options.LogoutPath = "/Account/Logout";
               options.Cookie.Expiration = TimeSpan.FromMinutes(5);
               options.Cookie.HttpOnly = true;
               options.Cookie.SecurePolicy = CookieSecurePolicy.None;
               options.Cookie.SameSite = SameSiteMode.Lax;
           });

Configuring HttpOnly marks whether cookies are only for service-side use and cannot be accessed directly from the front-end.

Configuring SecurePolicy will restrict cookies to HTTPS, which is recommended in production environments while supporting HTTPS.

Configure SameSite to indicate whether browsers can use cookies with cross-site requests. For OAuth authentication, set to Lax to allow external link redirection to issue POST requests, for example, and to maintain sessions. For Cookie authentication, set to Restrict, because cookie authentication only applies to a single site, if setIf set to None, the Cookie Header value will not be set.(Note: The SameSite property has been implemented in both Google and Firefox browsers. It does not seem to be supported in IE11. Safari has supported this property since version 12.1)

When creating a.NET Core default Web application, the global Cookie policy is directly configured through the middleware in the ConfigureServices method as follows:

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

Of course, the global Cookie policy is configured by default and used in the Configure method as follows:

            app.UseCookiePolicy();

We can also set the corresponding parameter policy directly by calling the method using the Cookie policy middleware above, as follows:

If we also configure the global Cookie policy when we add Cookie middleware, we will find that both the attributes HTTPOnly and SameSite are configurable, at which point personal guess will be overridden as follows:

 

For controllers that need to be certified we need to add [Authroize] features, and for each controller we have to add such a feature that we believe most children's shoes do.In fact, we can do the opposite, adding anonymous access features for controllers that do not require authentication, and configuring authentication filters globally for controllers that require authentication, as follows:

 services.AddMvc(options=> options.Filters.Add(new AuthorizeFilter()))
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Okay, here we just give a rough explanation of the Cookie middleware parameter configuration and Cookie global configuration strategy. We didn't go too far into the details. Let's wait for the problem to be analyzed.Going back to the topic, Cookie authentication is less secure than JWT for API access, so we can use it in conjunction with JWT in Cookie authentication.What exactly can we try?I think it should be possible to put it in the identity statement. Let's try simulated Login and login. The code is as follows:

    public class AccountController : Controller
    {
        /// <summary>
        /// Sign in
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> Login()
        {            
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, "Jeffcky"),
                new Claim(JwtRegisteredClaimNames.Email, "2752154844@qq.com"),
                new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"),
                new Claim("access_token", GenerateAccessToken()),
            };

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

            var authticationProperties = new AuthenticationProperties();

            await HttpContext.SignInAsync(
              CookieAuthenticationDefaults.AuthenticationScheme,
              new ClaimsPrincipal(claimsIdentity),
              authticationProperties);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

        string GenerateAccessToken()
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));

            var token = new JwtSecurityToken(
                issuer: "http://localhost:5000",
                audience: "http://localhost:5001",
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddHours(1),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        /// <summary>
        /// Sign out
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

    }

The above code is very simple, and I don't need to go any further. It's just like Cookie authentication. We added access_token to the declaration to improve security. Next, we customize an Action filter feature and apply it to the Action method as follows:

    public class AccessTokenActionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var principal = context.HttpContext.User as ClaimsPrincipal;

            var accessTokenClaim = principal?.Claims
              .FirstOrDefault(c => c.Type == "access_token");

            if (accessTokenClaim is null || string.IsNullOrEmpty(accessTokenClaim.Value))
            {
                context.HttpContext.Response.Redirect("/account/login", permanent: true);

                return;
            }

            var sharedKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));

            var validationParameters = new TokenValidationParameters
            {
                ValidateAudience = true,
                ValidIssuer = "http://localhost:5000",
                ValidAudiences = new[] { "http://localhost:5001" },
                IssuerSigningKeys = new[] { sharedKey }
            };

            var accessToken = accessTokenClaim.Value;

            var handler = new JwtSecurityTokenHandler();

            var user = (ClaimsPrincipal)null;

            try
            {
                user = handler.ValidateToken(accessToken, validationParameters, out SecurityToken validatedToken);
            }
            catch (SecurityTokenValidationException exception)
            {
                throw new Exception($"Token failed validation: {exception.Message}");
            }

            base.OnActionExecuting(context);
        }
    }

JWT Combine Cookie Authentication

If this is the case with putting JWT on a statement, I think it's okay to do so, at least I can't find any inappropriate place to do so.We can also mix Cookie certification with JWT certification, just adding Cookie middleware to the previous section, as shown below:

With the above configuration, we can combine Cookie and JWT authentication. For example, after a user logs in, click on the image below to display the current logged in user name, then click Exit to add the combination feature on the Exit Action method:

        /// <summary>
        /// Sign out
        /// </summary>
        /// <returns></returns>
        [Authorize(AuthenticationSchemes = "Bearer,Cookies")]
        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

In the previous section, we obtained the current time by getting AccessToken and accessing a client with port number 5001. Now we need Cookie authentication to add a method for obtaining the current time, as follows:

        [Authorize(CookieAuthenticationDefaults.AuthenticationScheme)]
        [HttpGet("api/[controller]")]
        public string GetCurrentTime()
        {
            var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;

            return DateTime.Now.ToString("yyyy-MM-dd");
        }

Cookie Certification Revocation

In.NET Core 2.1 authentication via cookies, when a user interacts with an application to modify information, the entire lifecycle of the cookie is required, that is, when no information changes are seen before the cookie is logged off or expired, we can do so through the cookie's authentication event [Revoke Identity]Let's take a look.

    public class RevokeCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        private readonly IDistributedCache _cache;

        public RevokeCookieAuthenticationEvents(
          IDistributedCache cache)
        {
            _cache = cache;
        }

        public override Task ValidatePrincipal(
          CookieValidatePrincipalContext context)
        {
            var userId = context.Principal?.Claims
            .First(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value;

            if (!string.IsNullOrEmpty(_cache.GetString("revoke-" + userId)))
            {
                context.RejectPrincipal();

                _cache.Remove("revoke-" + userId);
            }

            return Task.CompletedTask;
        }
    }

We override the ValidatePrincipal in the CookieAuthenticationEvents event to determine if the user representation exists written in memory and, if so, call context.RejectPrincipal() to revoke user identity.We then configure the event type in the Add Cookie Middleware and register it:

 services.AddScoped<RevokeCookieAuthenticationEvents>();

Next, we write a way to click Modify Information on the page and set the Undo Designated User in memory as follows:

        [HttpPost]
        public IActionResult ModifyInformation()
        {
            var principal = HttpContext?.User as ClaimsPrincipal;

            var userId = principal?.Claims
              .First(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value;

            if (!string.IsNullOrEmpty(userId))
            {
                _cache.SetString("revoke-" + userId, userId);
            }
            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

From the picture above, we can see that when you click on the modify information, then write the revoked user ID into memory, then jump to the Index page, call the revocation event we wrote, and eventually redirect to the login page, and the user cookie is not expired, so we can see the user name in the upper left corner.It is not clear under what circumstances this scenario will be used.

Redirect to login carry or remove parameters

When we operate on a page, if Token or Cookie is out of date, the user will be automatically guided and the URLs currently visited by the user will be taken along and redirected to the login page for login, such as the following jump URLs about the blog park:

https://account.cnblogs.com/signin?returnUrl=http%3a%2f%2fi.cnblogs.com%2f

But if we have a business scenario where when jumping to the login page, we need to take extra parameters on the URL, and we need to get this business parameter to do the corresponding business processing, what should we do at this point?We are still overriding the RedrectToLogin method in the CookieAuthenticationEvents event, as follows:

    public class RedirectToLoginCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        private IUrlHelperFactory _helper;
        private IActionContextAccessor _accessor;
        public RedirectToLoginCookieAuthenticationEvents(IUrlHelperFactory helper,
            IActionContextAccessor accessor)
        {
            _helper = helper;
            _accessor = accessor;
        }

        public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
        {
            //Get Routing Data
            var routeData = context.Request.HttpContext.GetRouteData();

            //Getting route values in routing data
            var routeValues = routeData.Values;

            var uri = new Uri(context.RedirectUri);

            //Resolution Jump URL Query parameters
            var returnUrl = HttpUtility.ParseQueryString(uri.Query)[context.Options.ReturnUrlParameter];

            //add extra parameters for redirect to login
            var parameters = $"id={Guid.NewGuid().ToString()}";

            //Adding extra parameters to routing values
            routeValues.Add(context.Options.ReturnUrlParameter, $"{returnUrl}{parameters}");

            var urlHelper = _helper.GetUrlHelper(_accessor.ActionContext);

            context.RedirectUri = UrlHelperExtensions.Action(urlHelper, "Login", "Account", routeValues);

            return base.RedirectToLogin(context);
        }
    }

Note here that because we used IActionContextAccessor above, we need to register it as follows:

 services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

Finally, when we jump to the login page, we will see that the extra parameter id we added will also appear on the url, as follows:

http://localhost:5001/Account/Login?ReturnUrl=%2FAccount%2FGetCurrentTime%3Fid%3Da309f451-e2ff-4496-bf18-65ba5c3ace9f

summary

This section describes the pros and cons of Session and JWT, and what we might need to use in Cookie certification. The next section is also the last section of JWT. We will talk about and explore how to refresh Token. Thank you for reading.

Keywords: Python Session Database encoding network

Added by aznjay on Mon, 29 Jul 2019 02:40:15 +0300