Search code examples
authenticationjwtasp.net-core-mvc.net-7.0

ASP.NET Core 7 MVC : authentication using JSON web tokens


I am developing an ASP.NET Core 7 MVC project. I am pretty new to ASP.NET Core and web development in general. I am almost done with the application functionality but I hadn't tried implementing the security of the application. Now I am trying to protect some of my urls. I have little bit of experience using Json Web Token in node js (express).

I have made some research and I managed to create the Jwt using the following method

private string CreateToken(string user)
{
    List<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, user)
    };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:JwtSecret").Value!));
    var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
    var token = new JwtSecurityToken(
        claims: claims,
        expires: DateTime.Now.AddMinutes(60),
        signingCredentials: cred
    );

    var jwt = new JwtSecurityTokenHandler().WriteToken(token);

    return jwt;
}

and I send It back to the user after the validation process

[HttpPost]
public IActionResult Authenticate(string user,string passwd)
{
    //validation logic
    if (someLogic)
    {
        string token = CreateToken(user);
        Response.Cookies.Append("TokenCookie", token);

        // Redirect the user to a protected page
        return RedirectToAction("Index", "Home");
    }
    else
    {
        TempData["ErrorLogin"] = "Invalid credentials";
        return RedirectToAction("Index", "Login");
    }         
}

According to what I have read up until this point. The usage of the Startup.cs file is no longer required so in my Program.cs file I added the following configuration.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            ValidateAudience = false,
            ValidateIssuer = false,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection("AppSettings:JwtSecret").Value!))
        };
    });

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseAuthorization();

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Login}/{action=Index}/{id?}");

app.Run();

The following step would be using the [Authorize] attribute in one of the routes that I want to protect but I get a 401 when I hit the protected url.

I don't know how the middleware that the application is using works, I I am not sure how it is supposed to get the json from the action. Where do I attach the jwt? Are the cookies a good way?

I am pretty lost so I would like to get someone else's opinion, feedback, any advice. What can I do from here? Am I following the right way? Do I go some steps back or further?


Solution

  • I think I was facing the wrong direction all this time.

    • First of all HTTP requests and responses do not save any information for future interactions with the server. So once you send the jwt back to the client the jwt will not be magically added to the next request.
    • It's the client's job to send the jwt along with the request. Therefore the client needs a way to store the jwt until the client sends the request. Here's when the cookies come in.

    I store the jwt on the client's cookies like so

    string token = CreateToken(user);
    
    Response.Cookies.Append("jwt", token, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.None
    });
    
    return RedirectToAction("Index", "Home");
    

    This way the jwt will be stored on the user's cookies.

    As said by @MdFaridUddinKiron

    As you have written token in cookie, so while reading from header have you checked if your header contains the token you have written? In addition, did you able to extract token from cookie also?

    We need a way to take this from the cookies and attach it to the request header. In order to solve this I created a Middleware class that does the following.

    public class JwtCookieToHeaderMiddleware
    {
        private readonly RequestDelegate _next;
    
        public JwtCookieToHeaderMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext context)
        {
            // Check if the "Authorization" header is present
            if (!context.Request.Headers.ContainsKey("Authorization") && context.Request.Cookies.TryGetValue("jwt", out string jwtToken))
            {
                // Add "Authorization" along with the jwt stored on the cookies
                context.Request.Headers.Add("Authorization", $"Bearer {jwtToken}");
            }
    
            // call next middleware in the pipeline
            await _next(context);
        }
    }
    

    In the Program.cs file we add the Middleware.

    app.UseMiddleware<JwtCookieToHeaderMiddleware>();
    app.UseAuthentication();
    app.UseAuthorization();
    

    This way every time the Authentication Middleware runs the Custom Middleware is going to happen first according to the pipeline order.