Vishal Ghosh
Vishal Ghosh

Reputation: 737

ASP.NET core JWT authentication always throwing 401 unauthorized

I'm trying to implement JWT authentication on my asp.net core webAPI as simply as possible. I don't know what i'm missing but it's always returning 401 even with the proper bearer token.

here is my configureServices code

public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(
               x =>
               {
                   x.RequireHttpsMetadata = false;
                   x.SaveToken = true;
                   x.TokenValidationParameters = new TokenValidationParameters
                   {
                       ValidateIssuerSigningKey = true,
                       IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("A_VERY_SECRET_SECURITY_KEY_FOR_JWT_AUTH")),
                       ValidateAudience = false,
                       ValidateIssuer = false,
                   };
               }
                );
            services.AddControllers();

            services.AddDbContext<dingdogdbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("dingdogdbContext")));
        }

and this is how I'm generating token

        [AllowAnonymous]
        [HttpPost("/Login")]
        public ActionResult<User> Login(AuthModel auth)
        {
            var user = new User();
            user.Email = auth.Email;
            user.Password = auth.Password;
            //var user = await _context.User.SingleOrDefaultAsync(u=> u.Email == auth.Email && u.Password==auth.Password);
            //if(user==null) return NotFound("User not found with this creds");

            //starting token generation...
            var tokenHandler = new JwtSecurityTokenHandler();
            var seckey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("A_VERY_SECRET_SECURITY_KEY_FOR_JWT_AUTH"));
            var signingCreds = new SigningCredentials(seckey, SecurityAlgorithms.HmacSha256Signature);
            var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
            {
                Subject = new System.Security.Claims.ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, user.Id.ToString()) }),
                SigningCredentials = signingCreds,
                Expires = DateTime.UtcNow.AddDays(7),
            });
            user.Token = tokenHandler.WriteToken(token);
            return user;
        }

And I added app.useAuthorization() very after the app.useRouting(). when i'm sending POST request to /Login I'm getting the token. but when I'm using the token in for querying any other endpoint using postman(added the token in authorization/JWT in postman) getting 401 unauthorized every time. is there anything I'm missing still?

Upvotes: 43

Views: 49686

Answers (10)

Dinariys
Dinariys

Reputation: 39

There was a similar problem on ASP.NET Core 8.0. Setting the property solved the problem: (https://stackoverflow.com/a/78160125/5173741): UseSecurityTokenValidators = true;

builder.Services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
    x.RequireHttpsMetadata = false;
    x.SaveToken = true;
    x.UseSecurityTokenValidators = true;
    x.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(KEY)),

    };
});

builder.Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

Previously, an error occurred before the change: bearer error = invalid_token

The log also contained such an error:

Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7] Bearer was not authenticated. Failure message: IDX14100: JWT is not well formed, there are no dots (.). The token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.

Upvotes: 1

Ali Shan
Ali Shan

Reputation: 527

I was having the same problem and could not find the solution until the end of day, so I closed the visual studio and shutdown the system. When I started the system next day the authentication started working in postman MAGICALLY without making any changes. So if you are stuck, give this a try.

Upvotes: -1

FatalError
FatalError

Reputation: 964

In case it might be helpful for some.

Make sure you are adding "Bearer" before the authentication token. In Swagger input field, I was putting only the token without "Bearer" text, raising same error.

Upvotes: 1

Harjinder Singh
Harjinder Singh

Reputation: 223

After struggling with this issue for hours, and trying different things from the multiple questions. Here is how I identified the problem:

Turn on the logging for Microsoft.AspNetCore.Authentication package in your appsettings.json file

{
  "Logging": {
    "Console": {
      "LogLevel": {
        "Microsoft.Hosting.Lifetime": "Trace",
        "Microsoft.AspNetCore.Authentication": "Information"
      }
    }
  },
// other attributes
}

This will log the authentication failures to the Visual Studio server console.

enter image description here

In my case, I was not passing the expires parameter to the new JwtSecurityToken constructor while generating the token. But I was validating the token lifetime in the TokenValidationParameters constructor.

Upvotes: 14

konkri
konkri

Reputation: 235

I had the same issue over and over again. I figured out that it was because the TokenValidationParameters in .AddJwtBearer() have to be like:

options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidateAudience = true,
        ValidAudience = builder.Configuration["Jwt:Audience"],
    };

Only in that case I had it working.

Upvotes: 0

BNG016
BNG016

Reputation: 310

Recap

In my case I was not using any Identity Server Yet I was providing the Host as a ValidIssuer. It validated the Authority for the algo and keys which returned nothing, this caused the system to throw an unhandled exception. Solved this By Removing options.Authority from JwtBearerOptions in AddJwtBearer(options => ...).

After that I faced the 401 ERROR, resolved it by removing options.Audience from JwtBearerOptions in AddJwtBearer(options => ...), Also added ValidateLifetime to TokenValidationParameters (which you can see below in part 1)


Code

PART (1) JWT Configuration

in .NET 6 :

builder.services.AddAuthentication(options => 
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => 
 {
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters() 
    {
       ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey,
       IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)),
       ValidateIssuer = jwtSettings.ValidateIssuer,
       ValidIssuer = jwtSettings.ValidIssuer,
       ValidateAudience = jwtSettings.ValidateAudience,
       ValidAudience = jwtSettings.ValidAudience,
       RequireExpirationTime = jwtSettings.RequireExpirationTime,
       ValidateLifetime = jwtSettings.RequireExpirationTime,
       ClockSkew = TimeSpan.FromDays(1),
    };
});

Extra

GET your JWT Settings from Appsettings using Either this Where

"JsonWebTokenKeys"

is the name of section in configuration :

var jwtSettings = new JwtSettings();
Configuration.Bind("JsonWebTokenKeys", jwtSettings);
builder.services.AddSingleton(jwtSettings);

//PART (1) => JWT Configuration goes here
//..
//.. 

OR this :

services.Configure<JwtSettings>(configuration.GetSection("JsonWebTokenKeys"));
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
   var jwtSettings = serviceProvider.GetRequiredService<IOptions<JwtSettings>>().Value;
   
   //PART (1) => JWT Configuration goes here
   //..
   //.. 
}
           

Upvotes: 1

Jakub Kozera
Jakub Kozera

Reputation: 3473

Keep in mind that the UseAuthentication, UseRouting and UseAuthorization middleware must in correct in order for the ASP framework properly inject the identity context to http request.

It should look like this: (.NET Core 3.1)

Edit: the same code applies to .NET 5 & .NET 6

            app.UseAuthentication();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

Upvotes: 112

Hoque MD Zahidul
Hoque MD Zahidul

Reputation: 11949

Step 1 : First make sure the order of the configure method in the stratup.cs class :

below i have given the valid order form for asp.net core 3.1

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

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

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

If Step one not working then try Step 2: make sure the token validation parameter and the token generation parameter & algorithm are same for that go to the ConfigureServices method of the startup.cs class and also go to the class or method where you have generated the token in my case it was UserService class

ConfigureServices method code :

public void ConfigureServices(IServiceCollection services)
    {
        var connectionString = Configuration.GetConnectionString("mySQLConnectionString");

        services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(connectionString));
        services.AddIdentity<IdentityUser, IdentityRole>(options =>
        {
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequiredLength = 5;
        }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

        services.AddAuthentication(auth =>
        {
            auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            
        }).AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidAudience = Configuration["AuthSettings:Audience"],
                ValidIssuer = Configuration["AuthSettings:Issuer"],
                RequireExpirationTime = true,
                IssuerSigningKey =
                    new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(Configuration["AuthSettings:key"])),
                ValidateIssuerSigningKey = true,

            };
        });
        services.AddScoped<IUserService, UserService>();
        services.AddControllers();
    }

Token Generation code :

 public async Task<UserManagerResponse> LoginUserAsync(LoginVIewModel model)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if(user == null)
        {
            return new UserManagerResponse
            {
                Message = "There is no user with that email",
                iSSuccess= false
            };
        }
        var result = await _userManager.CheckPasswordAsync(user, model.Password);
        if(! result)
        {
            return new UserManagerResponse
            {
                Message = "Your Provided password not match eith our system ",
                iSSuccess = false
            };

        }

        var clims = new[]
        {
            new Claim("Email", model.Email),
            new Claim(ClaimTypes.NameIdentifier, user.Id)
        };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:key"]));
        var token = new JwtSecurityToken(
            issuer: _configuration["AuthSettings:Issuer"],
            audience: _configuration["AuthSettings:Audience"],
            claims: clims,
            expires: DateTime.Now.AddDays(30),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );
        string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);

        return new UserManagerResponse
        {
            Message = tokenAsString,
            iSSuccess = true,
            ExpireDate = token.ValidTo
        };
    }
}

also please note that , In my case I have some spelling mistake in appsetting.json For example in the token generate code i have called the Audince but in the appSetting.json it was Audience . thats why both Audience not match .

             audience: _configuration["AuthSettings:Audince"]

Appsetting.json code :

"AllowedHosts": "*",
  "AuthSettings": {
    "key": "TThis is mfw sec test token",
    "Audience": "www.mfw.com",
    "Issuer": "www.mfw.com"
  }

Upvotes: 28

Warren Parad
Warren Parad

Reputation: 4074

There are some other issues here, that you may want to take a look at and potentially improve. The login mechanism currently contains a token that has a 7 days expiry. That means exposed tokens will still allow an attacker to access and impersonate the user for 7 days. In general it would be better to:

  • Log the user in and generate a token which is only good for 1 hour
  • Give the user a permanent device token to represent the device
  • Validate the device and token (even when expired) and possibly generate a new token.

This gives the user the ability to "log out" of all sessions in case something is compromised. Specifically these sorts of functionality and more are available by most authentication providers such as Auth0 or authorization providers such as Authress.

Upvotes: 0

Alok Kumar
Alok Kumar

Reputation: 649

First you need to check that the JWT token generated using your configureServices code is valid or not.To validate JWT token you can use JWT debugger. It will parse the JWT token value into each parameter by which you can verify that which of the parameter values assigned incorrectly and JWT debugger also provide you JWT valid or invalid. Once you figure this out you can work on identified errors or next course of action.

Upvotes: 3

Related Questions