Search code examples
jwtswaggeridentity.net-6.0bearer-token

Invalid JWT bearer token on .net 6.0 web api


I am trying to implement security to my app and used Identity and JWT bearer token to authenticate but I always get the invalid token response in my swagger. Tried many solutions but I still get the same response.

[HttpPost("[action]")]
        public async Task<IActionResult> Login(LoginBindingModel login)
        {
            IActionResult actionResult;
            var user = await userManager.FindByEmailAsync(login.Email);
            if (user == null)
            {
                actionResult = NotFound(new {errors = new[] {$"User with email {login.Email} is not found."}});
            }
            else if (await userManager.CheckPasswordAsync(user, login.Password))
            {
                if(!user.EmailConfirmed)
                {
                    actionResult = BadRequest(new { errors = new[] { $"Email not confirmed." } });
                }
                else
                {
                    var token = GenerateTokenAsync(user);
                    actionResult = Ok(token);
                }
            }
            else
            {
                actionResult = BadRequest(new { errors = new[] { $"Password is not valid." } });
            }
           
            return actionResult;
        }

        private string GenerateTokenAsync(IdentityUser user)
        {
            IList<Claim> userClaims = new List<Claim>
            {
                new Claim("UserName", user.UserName),
                new Claim("Email", user.Email)
            };

            var x = jwtOptions.SecurityKey;
            return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
                claims: userClaims,
                expires: DateTime.UtcNow.AddMonths(1),
                signingCredentials: new SigningCredentials(jwtOptions.SecurityKey, SecurityAlgorithms.HmacSha256)));
        }

Program.cs

using ASPNetCoreMasterAPIAssignment2.Filters;
using DomainModels;
using Infrastructure.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Repositories;
using Services;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

builder.Services.AddSwaggerGen(x =>
{
    x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please insert JWT token with bearer into field",
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });
    x.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    }); 
});

builder.Services.AddScoped<IItemService, ItemService>();
builder.Services.AddScoped<IItemRepository, ItemRepository>();

builder.Services.AddDbContext<ItemDbContext>(opt =>
{
    opt.UseSqlServer(builder.Configuration.GetConnectionString("default"));
});

builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ItemDbContext>()
    .AddDefaultTokenProviders();

SecurityKey key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["jwt:secret"]));
builder.Services.Configure<JWTOptions>(_ => _.SecurityKey = key);

builder.Services.AddAuthentication(opt =>
{
    opt.DefaultAuthenticateScheme = "Bearer";
    opt.DefaultChallengeScheme = "Bearer";
    opt.DefaultScheme = "Bearer";
})
    .AddJwtBearer(opt =>
    {
        opt.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            IssuerSigningKey = key
        };
    });


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI();
}
else
{
    app.UseExceptionHandler("/error");
}

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

app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "default": "Server=localhost;Database=EfCoreDb;Trusted_Connection=True;"
  },
  "jwt": {
    "secret": "5a927360-790b-4ba5-bae1-09aa98364090"
  }
}

when i add the [Authorized] attribute to a controller, i get the following error enter image description here


Solution

  • invalid_token can be caused by a lot of cases.

    Try to view the root cause by handling the OnChallenge method (in particular the content of context.AuthenticateFailure):

    return builder.AddJwtBearer(options =>
    {
        options.Authority = issuer;
        options.Audience = audience;
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ClockSkew = new System.TimeSpan(0, 0, 30)
        };
        options.Events = new JwtBearerEvents()
        {
            OnChallenge = context =>
            {
                context.HandleResponse();
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                context.Response.ContentType = "application/json";
    
                // Ensure we always have an error and error description.
                if (string.IsNullOrEmpty(context.Error))
                    context.Error = "invalid_token";
                if (string.IsNullOrEmpty(context.ErrorDescription))
                    context.ErrorDescription = "This request requires a valid JWT access token to be provided";
    
                // Add some extra context for expired tokens.
                if (context.AuthenticateFailure != null && context.AuthenticateFailure.GetType() == typeof(SecurityTokenExpiredException))
                {
                    var authenticationException = context.AuthenticateFailure as SecurityTokenExpiredException;
                    context.Response.Headers.Add("x-token-expired", authenticationException.Expires.ToString("o"));
                    context.ErrorDescription = $"The token expired on {authenticationException.Expires.ToString("o")}";
                }
    
                return context.Response.WriteAsync(JsonSerializer.Serialize(new
                {
                    error = context.Error,
                    error_description = context.ErrorDescription
                }));
            }
        };
    });
    

    source: https://sandrino.dev/blog/aspnet-core-5-jwt-authorization#configuring-jwt-bearer-authentication

    In my case, it was resolved by updating some nuget, specially Microsoft.IdentityModel.Tokens : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1792