Hi Laurent, based on your description: decouple the front-end from the back-end and use secure APIs + I don't want a cookie; I want a JWT. Also, I want my system to protect my API(s) and require authentication to access the API functions, I think you can use individual accounts + JWT auth in your application just like what Bruce recommended, please allow me to extend here.
Firstly, you can keep using individual accounts in your front-end blazor application, and I trust you shall have a login method which might use await SignInManager.PasswordSignInAsync();
to validate the user sign-in request. If the user signed in successfully, you need to generate an access token for this user and this is what you need to do extra. The token-generation function shall be similar to codes below. Since the user already signed in, you would get user name and many other information, you can add them into the token claims depending on your business. The code below can be put into one of your backend api project, you can create similar methods in your blazor app as well.
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
private string GenerateAccessToken()
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Name, "username1"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
var token = new JwtSecurityToken(
issuer: "Test.com",
audience: "Test.com",
expires: DateTime.Now.AddHours(3),
claims: claims,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
return accessToken;
}
The token is required to be added into request header Authorization: Bearer {token}
to call the secured API.
using (HttpClient client = new HttpClient())
{
// Add necessary headers
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzxxxxliG6-xm54");
// Send a GET request to the API
HttpResponseMessage apiResponse = await client.GetAsync("https://localhost:7035/api/hello");
// Ensure the request was successful
apiResponse.EnsureSuccessStatusCode();
// Read the response content as a string
string responseBody = await apiResponse.Content.ReadAsStringAsync();
}
Next, in the API projects where need to receive access token, you can add codes below in Program.cs, it will help to validate the codes and return 401/403/correct response based on the token in request header. You can change the options based on your business, my codes below are just for test purpose so that I set many options to be false.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//var key = new SymmetricSecurityKey(securityService.Key);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "Test.com",
ValidAudience = "Test.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"))
};
});
Certainly, you might want to add some policy for authorization, asp.net core provides poliy-based auth which could help define custom validation logic. You can see this tutorial. You will have to create a TokenHandler
and a TokenRequirement
,
//added behind builder.Services.AddAuthentication().AddJwtBearer()
builder.Services.AddSingleton<IAuthorizationHandler, TokenHandler>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Token", policy =>
policy.Requirements.Add(new TokenRequirement()));
});
public class TokenRequirement : IAuthorizationRequirement
{
//public TokenRequirement(int minimumAge) =>
//MinimumAge = minimumAge;
//public int MinimumAge { get; }
}
using Microsoft.AspNetCore.Authorization;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
public class TokenHandler : AuthorizationHandler<TokenRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
{
if (context != null)
{
var httpContext = (DefaultHttpContext)context.Resource;
if (httpContext != null)
{
string jwtToken = httpContext.Request.Headers["Authorization"].ToString();
var handler = new JwtSecurityTokenHandler();
var res = jwtToken.Substring(7);
JwtSecurityToken token = handler.ReadJwtToken(res);
var claims = token.Claims;
if (token.Claims.Where(c => c.Type == "role" && c.Value == @"XXXX Authentication1").Count() > 0)
//context.Fail();
context.Succeed(requirement);
else
{
//context.Fail();
context.Succeed(requirement);
//throw new NullReferenceException("my custom message");
}
}
else
//context.Fail();
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Then you can add [Authorize(Policy = "Token")]
in front of controller class or action to apply this policy.
If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.
Best regards,
Tiny