Search code examples
c#mongodbasp.net-core-webapi.net-8.0asp.net-core-8

ASP.NET Core 8 Web API : JWT token always invalid


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.


Solution

  • Solution

    The solution was installing package Microsoft.IdentityModel.JsonWebTokens according to this. I do not understand how that resolved the issue, as I didn't have to do anything else than install it, but I know that it solved the problem.