When authenticating through JWT on ASP.NET Core 8 Web API, it always throws this error:
Bearer error="invalid_token"
regardless of what I do.
I have checked the token through jwt.io
, where everything looks normal.
I have also checked the Chrome network tab, where everything looks normal.
I have straight up no idea, since I followed a YouTube tutorial where everything worked, and as I am quite new to ASP.NET Core.
Repo: https://github.com/LarsSK06/Home-Server.Backend/tree/dev
Program.cs
:
using HomeServer.Data;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Microsoft.IdentityModel.Tokens;
using System.Text;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => {
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme{
In = ParameterLocation.Header,
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
options.OperationFilter<SecurityRequirementsOperationFilter>();
});
builder.Services.AddSingleton<MongoDBService>();
builder.Services.AddCors(options => {
options.AddDefaultPolicy(
policy =>
policy.WithOrigins("http://localhost:3000")
);
});
builder.Services.AddAuthentication().AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters{
ValidateIssuerSigningKey = true,
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
builder.Configuration.GetSection("AppSettings:Token").Value!
)
)
};
});
WebApplication app = builder.Build();
if(app.Environment.IsDevelopment()){
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DatabaseConnection": "mongodb://localhost:27017/dev"
},
"AppSettings": {
"Token": "jdiwlpmfpn210dff1kg92wagwagagrgrgrddgdgdrdrrggrdwdwafawfwafwafawfafawfffwwf"
}
}
Users.cs
(API controller for Users/
endpoint
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using HomeServer.Models;
using HomeServer.Data;
using HomeServer.Utilities;
using Microsoft.AspNetCore.Authorization;
namespace HomeServer.Controllers;
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly IMongoCollection<User>? _users;
private readonly IConfiguration _config;
public UsersController(MongoDBService mongoDBService, IConfiguration configuration)
{
_users = mongoDBService.Database?.GetCollection<User>("users");
_config = configuration;
}
[HttpGet, Authorize]
public async Task<ActionResult<IEnumerable<PublicUser>>> GetUsers()
{
if (_users is null)
return NotFound();
FilterDefinition<User>? filter = FilterDefinition<User>.Empty;
IAsyncCursor<User>? cursor = await _users.FindAsync(filter);
List<User>? users = await cursor.ToListAsync();
List<PublicUser> publicUsers = [];
foreach(User i in users)
publicUsers.Add(i.ToPublic());
return Ok(publicUsers);
}
[HttpGet("{id}")]
public async Task<ActionResult<PublicUser>> GetUser(int id)
{
if (_users is null)
return NotFound();
FilterDefinition<User>? filter = Builders<User>.Filter.Eq(i => i.Id, id);
IAsyncCursor<User>? cursor = await _users.FindAsync(filter);
User? first = await cursor.FirstOrDefaultAsync();
if (first is null)
return NotFound();
return first.ToPublic();
}
[HttpPost]
public async Task<ActionResult<PublicUser>> CreateUser(MutableUser data)
{
if (_users is null)
return NotFound();
FilterDefinition<User> conflictedUsersFilter =
Builders<User>.Filter.Eq(i => i.Email, data.Email);
IAsyncCursor<User>? conflictedUsersCursor =
await _users.FindAsync(conflictedUsersFilter);
List<User> conflictedUsers = await conflictedUsersCursor.ToListAsync();
if (conflictedUsers.Count > 0)
return Conflict();
User user = new()
{
Id = Generator.GetEpoch(),
Name = data.Name,
Password = BCrypt.Net.BCrypt.HashPassword(data.Password, 15),
Email = data.Email,
Admin = data.Email.Equals("[email protected]")
};
await _users.InsertOneAsync(user);
return CreatedAtAction(
nameof(GetUser),
new { id = user.Id },
user.ToPublic()
);
}
[HttpPost("LogIn")]
public async Task<ActionResult<PublicUser>> LogIn(Credentials credentials)
{
if (_users is null)
return NotFound();
FilterDefinition<User>? filter = Builders<User>.Filter.Eq(i => i.Email, credentials.Email);
IAsyncCursor<User>? users = await _users.FindAsync(filter);
User? user = await users.FirstOrDefaultAsync();
if (user is null)
return BadRequest();
if (!BCrypt.Net.BCrypt.Verify(credentials.Password, user.Password))
return Unauthorized();
string token = JWT.CreateToken(_config, user.ToPublic());
return Ok(token);
}
}
public class Credentials
{
public required string Email { get; set; }
public required string Password { get; set; }
}
User.cs
(MongoDB model)
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace HomeServer.Models;
public class User
{
[BsonId]
[BsonElement("_id"), BsonRepresentation(BsonType.ObjectId)]
public string? MongoId { get; set; }
[BsonElement("id"), BsonRepresentation(BsonType.Int32)]
public required int Id { get; set; }
[BsonElement("name"), BsonRepresentation(BsonType.String)]
public required string Name { get; set; }
[BsonElement("password"), BsonRepresentation(BsonType.String)]
public required string Password { get; set; }
[BsonElement("email"), BsonRepresentation(BsonType.String)]
public required string Email { get; set; }
[BsonElement("admin"), BsonRepresentation(BsonType.Boolean)]
public required bool Admin { get; set; }
public PublicUser ToPublic()
{
return new PublicUser
{
Id = Id,
Name = Name,
Email = Email,
Admin = Admin
};
}
public MutableUser ToMutable()
{
return new MutableUser
{
Name = Name,
Password = Password,
Email = Email
};
}
}
public class PublicUser
{
public required int Id { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
public required bool Admin { get; set; }
}
public class MutableUser
{
public required string Name { get; set; }
public required string Password { get; set; }
public required string Email { get; set; }
}
JWT.cs
(location of CreateToken
function)
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using HomeServer.Models;
using Microsoft.IdentityModel.Tokens;
namespace HomeServer.Utilities;
public struct JWT
{
public static string CreateToken(IConfiguration config, PublicUser user)
{
List<Claim> claims = new List<Claim>
{ new Claim(ClaimTypes.Email, user.Email) };
SymmetricSecurityKey key =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
config.GetSection("AppSettings:Token").Value!)
);
SigningCredentials credentials =
new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
JwtSecurityToken token = new JwtSecurityToken(
claims: claims,
expires: DateTime.Now.AddHours(12),
signingCredentials: credentials
);
string jwt = new JwtSecurityTokenHandler().WriteToken(token);
return jwt;
}
}
Commits can be seen in repository, but besides that I’ve tried most YouTube tutorials to the detail, without any success.
I’ve tried adding different options to AddSwaggerGen
, AddAuthentication
, AddJwtBearer
, but I can’t figure it out.
2
Answers
With the following setup it seems to work:
There is an issue when using Microsoft.AspNetCore.Authentication.JwtBearer version 8 with an older (transient in this case) version of System.IdentityModel.Tokens.Jwt (version 7):
link.
The solution is to upgrade System.IdentityModel.Tokens.Jwt to version 8.1.2 from NuGet, then the line RequireExpirationTime = false can be removed, otherwise the expiration of the JWT is ignored and is a security issue.
JWT usually uses the Bearer authentication type instead of ApiKey. You can try changing
SecuritySchemeType.ApiKey
toSecuritySchemeType.Http
and set Scheme to Bearer as shown in the following example.When I get the token and add it to the request header:
I can successfully access the authorization method: