I am new to asp.net identity and jwt token authentification and having trouble authorizing API endpoints, or more exactly making calls to enpoints that require authorization. I created an Asp.net Core API with a custom database to which i added Identity on the user table. Everything worked fine untill i added the JWT Authentication. Then all the enpoints that require authorization fail : 401 UNAUTHORIZED Bearer error=”invalid_token”,error_description=”The signature key was not found”.
This is what i have in Program.Cs :
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Logging.AddConsole();
var host = builder.Configuration["DBHOST"];
var port = builder.Configuration["DBPORT"];
var user = builder.Configuration["DBUSER"];
var password = builder.Configuration["DBPASSWORD"];
var database = builder.Configuration["DBNAME"];
builder.Services.AddCors(policy =>
{
policy.AddPolicy("CorsAllAccessPolicy", opt =>
opt.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
var connectionString = $"Server={host},{port};Database={database};User Id={user};Password={password};TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true;Integrated Security=False;";
//var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GrdbContext>(options => options.UseSqlServer(connectionString));
ConfigureAutoMapper(builder.Services);
builder.Services.AddScoped<IDbService, DbService>();
builder.Services.AddSingleton(x => new BlobServiceClient("DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://storage:10000/devstoreaccount1;QueueEndpoint=http://storage:10001/devstoreaccount1;"));
builder.Services.AddIdentity<GrdbUser, IdentityRole<int>>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<GrdbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = configuration["JWT:VAlidAudience"],
ValidIssuer = configuration["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:SecretKey"]))
};
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
//builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "GRDB Database", Version = "v1" });
// Define JWT bearer token authentication
var securityScheme = new OpenApiSecurityScheme
{
Name = "Authorization",
Description = "JWT Authorization header using the Bearer scheme",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
};
c.AddSecurityDefinition("Bearer", securityScheme);
// Make sure Swagger UI requires a Bearer token
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<GrdbContext>();
InitializeDatabase(dbContext);
}
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
// app.UseSwagger();
// app.UseSwaggerUI();
//}
app.UseSwagger();
app.UseSwaggerUI();
app.UseCors("CorsAllAccessPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
static void ConfigureAutoMapper(IServiceCollection services)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<BookAuthor, AuthorDTO>().ReverseMap();
cfg.CreateMap<BookAuthor, AuthorCreateDTO>().ReverseMap();
cfg.CreateMap<BookGenre, GenreDTO>().ReverseMap();
cfg.CreateMap<BookGenre, GenreCreateDTO>().ReverseMap();
cfg.CreateMap<BookAuthorConnection, AuthorDTO>().ReverseMap();
cfg.CreateMap<BookGenreConnection, GenreDTO>().ReverseMap();
cfg.CreateMap<BookReview, BookReviewDTO>().ReverseMap();
cfg.CreateMap<BookReview, BookReviewCreateDTO>().ReverseMap();
cfg.CreateMap<BookReview, BookReviewUpdateDTO>().ReverseMap();
cfg.CreateMap<GrdbUser, GrdbUserDTO>().ReverseMap();
cfg.CreateMap<GrdbUser, GrdbUserCreateDTO>().ReverseMap();
cfg.CreateMap<GrdbUser, GrdbUserUpdateDTO>().ReverseMap();
cfg.CreateMap<Book, BookDTO>().ReverseMap();
cfg.CreateMap<Book, BookCreateDTO>().ReverseMap();
cfg.CreateMap<Book, BookUpdateDTO>().ReverseMap();
});
var mapper = config.CreateMapper();
services.AddSingleton(mapper);
}
public static void InitializeDatabase(GrdbContext dbContext)
{
// Apply any pending migrations
dbContext.Database.Migrate();
// Add your custom initialization logic here
// For example, you can seed initial data or create additional tables
// Save the changes to the database
dbContext.SaveChanges();
}
And this is what i have in appSettings :
"JWT": {
"SecretKey": "secretpa$$word",
"ValidIssuer": "grdb.adminui.server",
"ValidAudience": "grdb.clients",
"ExpirationInMinutes": 1440
}
Then i created an AuthenticationController that looks like this :
[Route("api/[controller]")]
[ApiController]
public class AuthenticateController : ControllerBase
{
private readonly UserManager<GrdbUser> _userManager;
private readonly RoleManager<IdentityRole<int>> _roleManager;
private readonly IConfiguration _configuration;
public AuthenticateController(
UserManager<GrdbUser> userManager,
RoleManager<IdentityRole<int>> roleManager,
IConfiguration configuration)
{
_userManager = userManager;
_roleManager = roleManager;
_configuration = configuration;
}
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
var userRoles = await _userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
if (authClaims.Count > 0)
{
var token = GetToken(authClaims);
return Ok(new AuthenticationResult
{
Token = new JwtSecurityTokenHandler().WriteToken(token),
Expiration = token.ValidTo,
User = new LoggedUser
{
Id = user.Id,
Username = user.UserName,
Email = user.Email,
Roles = (List<string>)userRoles
}
});
}
}
return Unauthorized();
}
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register([FromBody] RegisterModel model)
{
var userExists = await _userManager.FindByNameAsync(model.Username);
if (userExists != null)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });
GrdbUser user = new()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.Username,
};
var result = await _userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });
return Ok(new Response { Status = "Success", Message = "User created successfully!" });
}
//[Authorize(Roles = "Admin")]
[HttpPost]
[Route("register-admin")]
public async Task<IActionResult> RegisterAdmin([FromBody] RegisterModel model)
{
var userExists = await _userManager.FindByNameAsync(model.Username);
if (userExists != null)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });
GrdbUser user = new()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.Username
};
var result = await _userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });
if (!await _roleManager.RoleExistsAsync(UserRoles.Admin))
await _roleManager.CreateAsync(new IdentityRole<int>(UserRoles.Admin));
if (!await _roleManager.RoleExistsAsync(UserRoles.User))
await _roleManager.CreateAsync(new IdentityRole<int>(UserRoles.User));
if (await _roleManager.RoleExistsAsync(UserRoles.Admin))
{
await _userManager.AddToRoleAsync(user, UserRoles.Admin);
}
if (await _roleManager.RoleExistsAsync(UserRoles.Admin))
{
await _userManager.AddToRoleAsync(user, UserRoles.User);
}
return Ok(new Response { Status = "Success", Message = "User created successfully!" });
}
private JwtSecurityToken GetToken(List<Claim> authClaims)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:SecretKey"]));
var algorithm = SecurityAlgorithms.HmacSha256;
if (key.KeySize < 256)
{
// Generate a new key with the required size (128 bits)
var newKey = new byte[256 / 8];
new RNGCryptoServiceProvider().GetBytes(newKey);
key = new SymmetricSecurityKey(newKey);
}
var creds = new SigningCredentials(key, algorithm);
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
claims: authClaims,
expires: DateTime.UtcNow.AddHours(24),
signingCredentials: creds);
return token;
}
Then i swagger i can create an user and then upon login i get this :
swagger login
Then i use the token to Authorize Swagger and get authorized,but when i am trying to use a Post endpoint that need authorization i get this :
swagger_post
I have also tried to login and make HTTP requests (with headers) from a React app and from a Blazor app. The login works but the requests provide the same error.
Maybe usefull information : both the API and the SQL server run inside a multi project docker container using docker compose. I use for the registered users the tables that Asp.net Core Identity created.
Can someone please help me figure out what am i doing wrong! Thank you in advance!
tried different ways to setup jwt authentication and different ways to generate tokens, but failed
Luca Ionut is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.