Reputation: 737
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
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
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
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
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.
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
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
Reputation: 310
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)
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),
};
});
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
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
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
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:
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
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