Paul
Paul

Reputation: 218

ASP.NET Core 5 and 6 JWT authentication is always throws HTTP 401 code

I want to implement JWT-based security in ASP.NET Core. All I want it to do, for now, is to read tokens in the button @Html.ActionLink("Test","Oper","Home") , authorize header and validate them against my criteria. I don't know what missed but it is always returning HTTP 401 code.

File HomeController.cs

        private string GenerateJSONWebToken(UserPaul userinfo)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub,userinfo.Username),
                new Claim(JwtRegisteredClaimNames.Email,userinfo.Email),
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
            };
            var token = new JwtSecurityToken(
                issuer: _config["Jwt:Issuer"],
                audience: _config["Jwt:Issuer"],
                claims,
                expires: DateTime.Now.AddMinutes(10),
                signingCredentials: credentials
                );
            var encodetoken = new JwtSecurityTokenHandler().WriteToken(token);
            var cookieOptions = new CookieOptions();         
            cookieOptions.HttpOnly = true;
            cookieOptions.Expires = DateTime.Now.AddMinutes(1);
            //cookieOptions.Domain = Request.Host.Value;
            cookieOptions.Path = "/";
            Response.Cookies.Append("jwt", encodetoken, cookieOptions);
            return encodetoken;
        }
        [HttpPost]
        public IActionResult Login()
        {
            string AccountNumber="TestUser";
            JWTtokenMVC.Models.TestContext userQuery = new JWTtokenMVC.Models.TestContext();
            var query = userQuery.Testxxxx.Where(N => N.UserId ==AccountNumber).FirstOrDefault();
            IActionResult response = Unauthorized();
            if (query != null)
            {
                var tokenStr = GenerateJSONWebToken(query);
                response = Ok(new { token = tokenStr });
            }
            return response;
        }

        [Authorize]
        [HttpGet("Home/Oper")]
        public IActionResult Oper()
        {
            var authenticationCookieName = "jwt";
            var cookie = HttpContext.Request.Cookies[authenticationCookieName];
            List<Test_SHOW> sHOWs = new List<Test_SHOW>();
            JWTtokenMVC.Models.Test.TestContext userQuery= new JWTtokenMVC.Models.Test.TestContext();
            var query = userQuery.Test.Select(T => new Test_SHOW
            {number= T.number,name= T.name,mail= T.mail}).OrderBy(o => o.Iid);
            sHOWs.AddRange(query);

            return View("Views/Home/Oper.cshtml", sHOWs);
 
        }

This is the Startup.cs code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.FileProviders;
using System.IO;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace JWTtokenMVC
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build());
            });

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.IncludeErrorDetails = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {

NameClaimType ="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
                   
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
                    ValidateIssuer = true,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Jwt:Issuer"],
                    ValidAudience = Configuration["Jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])

                    )
                };

            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                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.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "node_modules")),
                RequestPath = "/" + "node_modules"
            });
            app.UseCookiePolicy();

            app.UseRouting();
            app.UseAuthentication();

            app.UseAuthorization();

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

Startup.cs image

enter image description here

Startup.cs Add UseAuthentication

Upvotes: 4

Views: 3718

Answers (4)

Paul
Paul

Reputation: 218

I try on asp.net core 6 configured JwtBearer JSON Web Tokens return HTTP 401 code same as before asp.net core 5 error. but adding what @Brett Caswell was saying to solve my problem.

code is as follows

.AddJwtBearer(options => {
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    context.Token = context.Request.Cookies["jwt"];
                    return Task.CompletedTask;
                }
            };
        });

My asp .net core 6 code
Program.cs

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
        options.SlidingExpiration = true;
        options.AccessDeniedPath = "/test/";
    })
    .AddJwtBearer(options =>
            {
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        context.Token = context.Request.Cookies["jwtTest"];
                        return Task.CompletedTask;
                    }
                };
                options.IncludeErrorDetails = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = builder.Configuration["JwtTest:Issuer"],
                    ValidAudience = builder.Configuration["JwtTest:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtTest:Key"])

                    )
                };

            });

because my app uses cookie-based authentication and JWT bearer authentication so my Controller adds [Authorize(AuthenticationSchemes=JwtBearerDefaults.AuthenticationScheme)] for jwt

HomeController

[Authorize(AuthenticationSchemes=JwtBearerDefaults.AuthenticationScheme)]
    [HttpGet("[action]")]
    public ActionResult ShowData()
    {
        var quetTest = dbTestContext.staffTestData.ToList();

        return View("Views/Home/test.cshtml", query);
    }

Upvotes: 0

gemini88mill
gemini88mill

Reputation: 170

I'm not sure how relevant this is to the original question but i thought i would chime in.

after struggling for a while on this issue, I ended up adding what @Brett Caswell was saying to solve my problem.

services.AddAuthentication(options => {
    //add configs
})
.AddJwtBearer(x => {
    //add more configs
    x.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    context.Token = context.Request.Headers["Authorization"];
                    return Task.CompletedTask;
                },
            };
})

This was after realizing that my JwtAuth scheme was providing tokens but once the token was being sent back for validation, it wasn't hitting anywhere in the startup or configs that i set up for my project. Add this to your .AddJwtBearer() block and you should be good.

Upvotes: 1

Brett Caswell
Brett Caswell

Reputation: 1504

@Patriom Sarkar's answer addresses your CORS issues/Error

In regards to your 401 Unauthorized Responses

Those are likely unrelated to CORS.

What you're having issues with here is that you have configured JwtBearer JSON Web Tokens to be present in requests for your Authorize endpoints. It will, by default behavior, use Bearer tokens that are present in your Authorization request header.

Which means, in order to navigate/call Oper(), you will need to ensure "Authorization: Bearer {token}" exists with a valid token (as a request header).

Currently, in your Login handling, you're generating the token and doing Set-Cookie so that the user-agent/client creates 'jwt' cookie with the token as a value; However, the user-agent is not going to automatically add that jwt cookie to the headers of subsequent requests as an Authorization: Bearer Token. So JwtBearer configuration on Authorize attributed endpoints aren't going to be valid.

Also worth noting, setting the headers in Xhr or Fetch operations is supported and normal in SPA frameworks (to include Cookie stored jwt_tokens). However, you are not doing Xhr or Fetch requests here, you're doing html form post workflow/mechanic - navigating pages/views. In this respect, setting Authorization Header client-side is (AFAIK) not possible to do.

To support page/view navigation here, you'll need to implement a solution on the server-side that sets the token using the passed jwt cookie.

That solution is covered in In ASP.NET Core read JWT token from Cookie instead of Headers by @Kirk Larkin

    .AddJwtBearer(options => {
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    context.Token = context.Request.Cookies["jwt"];
                    return Task.CompletedTask;
                }
            };
        });

Additionally

context.Token is always null or empty in this scope. No preprocessing nor postprocessing will occur with this declaration and assignment. If you intend to support Authorization Header and Cookie, you should implement that conditionality in this OnMessageReceived assigned delegate.

you can review the default handling of JwtBearerHandler (aspnetcore 5.0) on GitHub.

Thanks again to @Kirk Larkin for providing this additional information in a reply comment on the linked question.

Note

This answer was originally provided on a duplicate question: ASP.NET Core 5.0 JWT authentication is throws 401 code

Upvotes: 1

Pritom Sarkar
Pritom Sarkar

Reputation: 2252

So I assume you are trying asp.net core with an angular project.I think you are missed adding your client-side URL to your .net core project. AddCorson extension call for IServiceCollection just registers all required services, but it does not add Cors middleware to the HTTP request pipeline.So add this code app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200")); in your Configure method.i think it's resolve your issue.


 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                //clarify code
              
            }
            else{  

               //clarify code    

            }
            

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors(x => 
  x.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200")); //your  client side URL.you are missing this unfortunately

            app.UseAuthentication();

            app.UseAuthorization();

           //clarify code
        }

UPDATE

Install Microsoft.AspNetCore.Cors

Just remove AllowCredentials() and it's will fix your issue.

Upvotes: 2

Related Questions