I have a minimal api here. It creates me a JWT for authorization, and then it tests it. using the [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
attribute.
app.MapPost("/login", [AllowAnonymous]
async (HttpContext http, ITokenService tokenService, IUserRepositoryService userRepositoryService) =>
{
var userLogin = await http.Request.ReadFromJsonAsync<UserModel>();
var userDto = userRepositoryService.GetUser(userLogin);
if (userDto == null)
{
http.Response.StatusCode = 401;
return;
}
var token = tokenService.BuildToken(builder.Configuration["Jwt:Key"], builder.Configuration["Jwt:Issuer"],
builder.Configuration["Jwt:Audience"], userDto);
await http.Response.WriteAsJsonAsync(new { Token = token });
});
app.MapGet("/secretAction",
(Func<string>)([Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]() => "Action Succeeded")
);
The first method works just fine. A post to the login endpoint with a json string containing the login and password returns to me a jwt.
The second endpoint is always returning unauthorized.
I have been back and forth with this trying to understand why its not able to parse the token that it creates.
Any help would be greatly appreciated.
appsettings.json
"Jwt": {
"Key": "this-is-the-secret",
"Issuer": "https://jwtauth.example.com",
"Audience": "api1"
}
program.cs
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ITokenService>(new TokenService());
builder.Services.AddSingleton<IUserRepositoryService>(new UserRepositoryService());
builder.Services.AddAuthorization();
var key = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]);
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.RequireHttpsMetadata = false;
opt.SaveToken = true;
opt.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateIssuer = false,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateAudience = true,
ValidateLifetime = true,
};
});
await using var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapGet("/",
(Func<string>)(() =>
"Test JWT Authentication using Minimalist Web API .net 6. <br> /login UserName: user1, Password: test <br> /secretAction "));
app.MapPost("/login", [AllowAnonymous]
async (HttpContext http, ITokenService tokenService, IUserRepositoryService userRepositoryService) =>
{
var userLogin = await http.Request.ReadFromJsonAsync<UserModel>();
var userDto = userRepositoryService.GetUser(userLogin);
if (userDto == null)
{
http.Response.StatusCode = 401;
return;
}
var token = tokenService.BuildToken(builder.Configuration["Jwt:Key"], builder.Configuration["Jwt:Issuer"],
builder.Configuration["Jwt:Audience"], userDto);
await http.Response.WriteAsJsonAsync(new { Token = token });
});
app.MapGet("/secretAction",
(Func<string>)([Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]() => "Action Succeeded")
);
await app.RunAsync();
public record UserDto(string UserName, string Password);
public record UserModel
{
[Required] public string UserName { get; set; }
[Required] public string Password { get; set; }
}
public interface IUserRepositoryService
{
UserDto GetUser(UserModel userModel);
}
public class UserRepositoryService : IUserRepositoryService
{
private List<UserDto> _users => new()
{
new("User1", "test"),
};
public UserDto GetUser(UserModel userModel)
{
return _users.FirstOrDefault(x =>
string.Equals(x.UserName, userModel.UserName) && string.Equals(x.Password, userModel.Password));
}
}
public interface ITokenService
{
string BuildToken(string key, string issuer, string audience, UserDto user);
}
public class TokenService : ITokenService
{
private TimeSpan ExpiryDuration = new TimeSpan(0, 30, 0);
public string BuildToken(string key, string issuer, string audience, UserDto user)
{
var keyBytes = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var claims = new[]
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString())
};
var descriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(keyBytes, SecurityAlgorithms.HmacSha256Signature),
IssuedAt = DateTime.Now,
NotBefore = DateTime.Now,
Expires = DateTime.Now.AddDays(1)
};
var jwtHandler = new JwtSecurityTokenHandler();
var token = jwtHandler.CreateToken(descriptor);
return jwtHandler.WriteToken(token);
// alternatively
// return jwtHandler.CreateEncodedJwt(descriptor);
}
To test it.
const string jwtUrl = "https://localhost:7080/login";
var content = new StringContent("{\"UserName\" : \"User1\",\"Password\" : \"test\"}", Encoding.UTF8, "application/json");
var httpResponseMessage = await client.PostAsync(jwtUrl,content);
var jwt = await httpResponseMessage.Content.ReadAsStringAsync();
var queueMessage = JsonSerializer.Deserialize<TokenResponse>(jwt);
Console.WriteLine(queueMessage.token);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", queueMessage.token);
const string protectedUrl = "https://localhost:7080/secretAction";
var result = await client.GetStringAsync(protectedUrl);
Console.WriteLine(result);
Logs
Here is a JWT just created.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiVXNlcjEiLCJuYW1laWQiOiI2OWJmOTg5Ni0wYTIyLTQ1N2UtODkyMy00ZTM4MGQzMTEyNTkiLCJuYmYiOjE2NjcyOTA5NTgsImV4cCI6MTY2NzM3NzM1OCwiaWF0IjoxNjY3MjkwOTU4LCJpc3MiOiJodHRwczovL2p3dGF1dGguZXhhbXBsZS5jb20i LCJhdWQiOiJhcGkxIn0.aUxhJuOrNOHeId6vHpHe1ZqnuC2MJ4TaYi577Cc37oU
The logs from trying to access the secretAction sending the jwt as a bearer token.
System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) at Program.$(String[] args) in C:\Development\FreeLance\Glassix\asp-net-core-auth-with-self-generated-jwt\ConsoleApp1\Program.cs:line 27