There is a existing .NET Framework application that creates Bearer token. Another existing .NET Framework code application is able to recognize the token for authentication and authorization. RSA security keys are used so it is not as simple as my previous work on this topic.
My task is to create a .NET 7 application that is also able to recognize the same token. I did not succeed. That is why I isolated the problem in one .NET 7 application. Hopefully, someone can fix my extension method in such a way that this is going to work.
Let me show you my extension method:
public static IServiceCollection AddCustomJwtAuthentication(this IServiceCollection services, AuthConfiguration authConfiguration)
{
// Initialize RSA parameters for public key
var rsa = new RSACryptoServiceProvider(2048);
rsa.FromXmlString(authConfiguration.OAuthAccessTokenSigningKeyPublic);
var rsaPublicKey = new RsaSecurityKey(rsa.ExportParameters(false));
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authConfiguration.OAuthTokenEncryptionKey));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = "Bearer",
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = authConfiguration.OAuthIssuer,
ValidAudience = authConfiguration.OAuthAudience,
IssuerSigningKey = rsaPublicKey, // Used for verifying the token's signature
TokenDecryptionKey = symmetricSecurityKey, // Used for decrypting the token's payload
ValidAlgorithms = new[]
{
SecurityAlgorithms.RsaSha512Signature, // For signature verification
SecurityAlgorithms.Aes256KW, // For key wrapping during decryption
SecurityAlgorithms.Aes128CbcHmacSha256 // For content encryption/decryption
}
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine($"OnAuthenticationFailed {context.Exception}");
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Append("Token-Expired", $"{true}");
}
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine($"OnTokenValidated: with SecurityToken valid to {context.SecurityToken.ValidTo}");
return Task.CompletedTask;
},
OnChallenge = context =>
{
Console.WriteLine($"OnChallenge: {context.Error} - {context.ErrorDescription}");
return Task.CompletedTask;
}
};
});
return services;
}
I’ll show my tokencreator too:
public class TokenCreator
{
private const int KeySize = 2048;
private readonly string _issuer;
private readonly string _audience;
private readonly SymmetricSecurityKey _encryptionKey;
private readonly EncryptingCredentials _encryptingCredentials;
private SigningCredentials _signingCredentials;
private TokenCreator(string encryptionKey, string issuer, string audience)
{
if (string.IsNullOrWhiteSpace(encryptionKey)) throw new ArgumentNullException(nameof(encryptionKey));
_issuer = issuer;
_audience = audience;
_encryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(encryptionKey));
_encryptingCredentials = new EncryptingCredentials(
_encryptionKey,
SecurityAlgorithms.Aes256KW,
SecurityAlgorithms.Aes128CbcHmacSha256);
}
public TokenCreator(AuthConfiguration authConfiguration) : this (authConfiguration.OAuthTokenEncryptionKey,
authConfiguration.OAuthIssuer, authConfiguration.OAuthAudience)
{
SetSigningCredentials(authConfiguration.OAuthAccessTokenSigningKeyPrivate);
}
private void SetSigningCredentials(string privateSigningKeyXml)
{
if (string.IsNullOrWhiteSpace(privateSigningKeyXml)) throw new ArgumentNullException(nameof(privateSigningKeyXml));
using (var rsa = new RSACryptoServiceProvider(KeySize))
{
rsa.FromXmlString(privateSigningKeyXml);
_signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa.ExportParameters(true)), SecurityAlgorithms.RsaSha512Signature);
}
}
public virtual string CreateToken(DateTime expires, DateTime issued)
{
// Define claims
var claims = new List<Claim>
{
new(ClaimTypes.Name, "John Doe"),
new(ClaimTypes.Email, "[email protected]"),
new(ClaimTypes.Role, "Administrator"),
new("CustomClaimType", "CustomClaimValue")
};
var identity = new ClaimsIdentity(claims, "Bearer");
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(_issuer, _audience, identity, null, expires, issued, _signingCredentials, _encryptingCredentials);
var jwt = handler.WriteToken(token);
return jwt;
}
}
I’ll also share my program class:
public class Program
{
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();
var authConfiguration = builder.Configuration.GetSection(nameof(AuthConfiguration)).Get<AuthConfiguration>()!;
builder.Services.AddCustomJwtAuthentication(authConfiguration);
var tokenCreator = new TokenCreator(authConfiguration);
var token = tokenCreator.CreateToken(DateTime.UtcNow.AddHours(1), DateTime.UtcNow);
Console.WriteLine("Token is coming");
Console.WriteLine(token);
Console.WriteLine("End of token");
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
As this web api generates a token for me, I can directly test with VSCode REST Client:
GET https://localhost:7134/api/Authentication/verifyAuthentication
Authorization: Bearer SUPERLONGTOKEN
This to call my controller method:
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
[HttpGet("verifyAuthentication")]
[Authorize]
public IActionResult VerifyAuthentication()
{
return Ok("Auth succeeded");
}
}
This gives the following output:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
Date: Tue, 11 Jun 2024 12:26:12 GMT
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
Token generation succeeds so this is not my concern. To be able to work with it, I need to write an extension method using the same keys as my .NET Framework code uses. And apparently, I did not do that correctly as I do not get a 200 returned. So what needs to be changed to handle a Bearer token in .NET 7 when a RsaSecurityKey is needed?