DEV Community

Joma
Joma

Posted on

.NET Core: Custom middleware for authentication using JWT and Cookie

I will only focus on how to create a custom middleware and implement authentication logic using JSON web token stored as a cookie.

  1. IMiddleware interface

  2. InvokeAsync function which is inherited from the interface

  3. Custom login function which calls a GenerateJSONWebToken method if login credentials are correct

Here's my full code:

using Test.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace Test.Server.Middleware
{
    public class TestMiddleware : IMiddleware
    {
        private readonly TestDatabaseContext _dbContext;

        public TestMiddleware(TestContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            if (context.Request.Path.Equals("/auth/login", StringComparison.OrdinalIgnoreCase)
                && context.Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
            {
                await HandleLogin(context);
                return; 
            }

            await next.Invoke(context);
        }

        private async Task HandleLogin(HttpContext context)
        {

            if (!context.Request.HasFormContentType)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsJsonAsync(new { message = "Invalid content type." });
                return;
            }

            var form = await context.Request.ReadFormAsync();
            var username = form["KorisnikUsername"].ToString();
            var password = form["KorisnikPass"].ToString();

            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
            {
                context.Response.StatusCode = 401;
                await context.Response.WriteAsJsonAsync(new
                {
                    message = "Unesite kredencijale za pristupanje admin panelu!"
                });
                return;
            }

            var user = await _dbContext.PrijavaKorisnika
                .FirstOrDefaultAsync(u =>
                    u.KorisnikUsername == username &&
                    u.KorisnikPass == password
                );

            if (user != null)
            {
                try
                {
                    var tokenString = GenerateJSONWebToken(username);
                    context.Response.Cookies.Append("X-Access-Token", tokenString, new CookieOptions() { HttpOnly = true, SameSite = SameSiteMode.Strict, Secure = true });
                    context.Response.StatusCode = 200;
                    await context.Response.WriteAsync( tokenString );
                }
                catch
                {
                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsJsonAsync(new { message = "Nije moguce generisati sigurnosni kljuc!" });
                }

                return;
            }

            context.Response.StatusCode = 404;
            await context.Response.WriteAsJsonAsync(new
            {
                message = "Korisnik s ovim kredencijalima ne postoji u sistemu. Pokušajte još jednom!"
            });
        }

        private string GenerateJSONWebToken(string username)
        {
            //1. claims
            var claim = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, username),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            };

            //2.key
            var env = Environment.GetEnvironmentVariable("UserAccessToken");
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(env));

            //3. generate a token
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512);
            var token = new JwtSecurityToken(

                    issuer: "https://localhost:7171",
                    audience: "https://localhost:7171",
                    claims: claim,
                    expires: DateTime.Now.AddMinutes(60),
                    signingCredentials: creds
            );


            //4. handler, writetoken
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Explanation:

InvokeAsync function is inherited from a IMiddleware interface. We use it to do two security checks and call our custom login function.

HandleLogin is my custom login function which sends login credentials that come from a form (e.g.front-end application will send data using formData, such as React). If user is found (credentials are correct), then JSON web token will be created for that user only and stored as a cookie.

GenerateJSONWebToken is a method which returns a token created by providing a username (it is a payload) and stored as a cookie. Cookie is a much better and safer option than localStorage because token cannot be accessed by performing a XSS attack.

As you already know, middlewares can invoke the next one by calling next.Invoke(context). Our custom middleware will do the exact same thing if the requested path (URL) from front-end application doesn't include /auth/login part and the HTTP method is not a POST method. This is some sort of an explicit security check to ensure that the correct middleware and HTTP method are sent by a front-end application. When the HTTP method and URL are correct, the next part is all about credentials and how they are sent. I have chosen a form so I need to check whether the credentials are really sent from the form. This is a second security-check level. I extract two properties: KorisnikUsername and KorisnikPass from the form and send them to my database in order to check whether those two properties have correct key-value pairs or not. If the answer is YES, I then call a method to generate a proper JSON web token for that specific user and store it as a cookie.

You can test this solution in a Postman where you'll use a localhost URL with a /auth/login part, set a method to be a POST method and as a result get this kind of a response:

JWT as a cookie

As you could notice in my code, it's all about the HttpContext which has two main properties: Request and Response. Request comes from a front-end application and Response is a server/middleware response.

Key steps:

  1. Check for the requested URL and method

  2. Check for the proper content type

  3. Check for the credentials

  4. If credentials are correct, generate a token and store it as a cookie which is a much safer solution

Top comments (0)