Search code examples
oauthjwt.net-7.0.net-8.0

In JWT Authentication in .NET 7, how can a "validated" token be "missing"?


I am in a strange situation with my .NET 7 web app. According to my application logs, my token is not found but still validated in some way which does not make sense to me. I need to get my controller method called. I used this web page to understand JWT Authentication in .NET 7.

I have the following code in my Program.cs:

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        // Add services to the container.
        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.AddAuthentication(cfg => {
            cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            cfg.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options => {
            options.RequireHttpsMetadata = false;
            options.SaveToken = false;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes("UwSuperGeheimeSleutelDieLangGenoegIs12345678")
                ),
                ValidateIssuer = false,
                ValidateAudience = false,
                ClockSkew = TimeSpan.Zero
            };
            options.Events = new JwtBearerEvents
            {
                OnAuthenticationFailed = context =>
                {
                    Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
                    if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                    {
                        context.Response.Headers.Add("Token-Expired", "true");
                    }
                    return Task.CompletedTask;
                },
                OnMessageReceived = context =>
                {
                    Console.WriteLine("OnMessageReceived: " + context.Token);
                    if (string.IsNullOrEmpty(context.Token))
                    {
                        Console.WriteLine("Token is missing from the request.");
                    }
                    return Task.CompletedTask;
                },
                OnTokenValidated = context =>
                {
                    Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
                    return Task.CompletedTask;
                },
                OnChallenge = context =>
                {
                    Console.WriteLine("OnChallenge: " + context.Error + " - " + context.ErrorDescription);
                    return Task.CompletedTask;
                }
            };
        });
        var token = TokenGenerator.GenerateJWTToken("Daan", Guid.NewGuid());
        Console.WriteLine("TOKEN START");
        Console.WriteLine(token);
        Console.WriteLine("TOKEN END");
        var app = builder.Build();
        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }
        app.UseHttpsRedirection();
        app.UseAuthorization();
        app.UseAuthentication();
        app.MapControllers();
        app.Run();
    }
}

I have the following controller method:

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet("checkclaims")]
        [Authorize]
        public IActionResult CheckClaims()
        {
            throw new NotImplementedException("Not yet implemented");
        }
    }

And this is my token generation:

    public static class TokenGenerator
    {
        public static string GenerateJWTToken(string userName, Guid id)
        {
            var claims = new List<Claim> {
                new Claim(ClaimTypes.NameIdentifier, id.ToString()),
                new Claim(ClaimTypes.Name, userName),
            };
            var jwtToken = new JwtSecurityToken(
                claims: claims,
                notBefore: DateTime.UtcNow,
                expires: DateTime.UtcNow.AddDays(30),
                signingCredentials: new SigningCredentials(
                    new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes("UwSuperGeheimeSleutelDieLangGenoegIs12345678")
                    ),
                    SecurityAlgorithms.HmacSha256Signature)
            );
            return new JwtSecurityTokenHandler().WriteToken(jwtToken);
        }
    }

I try to call my web api like this in VSCode REST Client

GET https://localhost:7126/api/Values/checkclaims
Authorization: Bearer eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImVhZGRjYTdmLTBkYTMtNGU1Yy1iYzdkLThhM2Y3OWJmODg1ZCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJEYWFuIiwibmJmIjoxNzE3Njg1Nzk4LCJleHAiOjE3MjAyNzc3OTh9.Jwp70J6IfHHvkrG0uu0dXrBxHcRBNthhKzkTMW6wNjo

And this is my response of my application:

HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
Date: Thu, 06 Jun 2024 14:57:20 GMT
Server: Kestrel
WWW-Authenticate: Bearer

With this output:

OnMessageReceived:
Token is missing from the request.
OnTokenValidated: {"alg":"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256","typ":"JWT"}.{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"eaddca7f-0da3-4e5c-bc7d-8a3f79bf885d","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Daan","nbf":1717685798,"exp":1720277798}

Normally, logging helps me to solve an issue but this is just confusing.

Why isn't my controller method called? Why is my token "validated" but also "missing"?

And last but not least, how can I make sure my controller method is called?


Solution

  • I think you need to change the order of UseAuthorization() and UseAuthentication() in your startup.

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

    The UseAuthentication middleware is responsible for setting the User property of the HttpContext, which is then used by the UseAuthorization middleware to authorize the user. If UseAuthentication is called after UseAuthorization, the User property will not be set when the authorization middleware runs, resulting in an unauthorized error.