You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
PortfolioLink/backend/JwtAuthenticationMiddelware.cs

136 lines
5.9 KiB

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Hasher;
/// <summary>
/// Middleware for JWT authentication and payload signature verification.
/// </summary>
public class JwtAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the JwtAuthenticationMiddleware class.
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="configuration">The application configuration.</param>
/// <param name="logger">The logger for this middleware.</param>
public JwtAuthenticationMiddleware(RequestDelegate next, IConfiguration configuration, ILogger<JwtAuthenticationMiddleware> logger)
{
_next = next;
_configuration = configuration;
_logger = logger;
}
/// <summary>
/// Invokes the middleware for the specified context.
/// </summary>
/// <param name="context">The HttpContext for the current request.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
_logger.LogDebug($"Request cookies: {context.Request.Cookies}");
// Read the Authorization and UserFingerprint cookies
context.Request.Cookies.TryGetValue("__Secure-Authorization", out string token);
context.Request.Cookies.TryGetValue("__Secure-UserFingerprint", out string fingerprint);
// Check if both token and fingerprint are present
if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(fingerprint))
{
// Validate the JWT and fingerprint
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["JwtSettings:issuer"],
ValidAudience = _configuration["JwtSettings:audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:SecretKey"])),
ClockSkew = TimeSpan.Zero
};
try
{
// Validate the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
// Extract claims from the validated token
var jwtToken = (JwtSecurityToken)validatedToken;
var claims = jwtToken.Claims;
// Validate the fingerprint
var fingerprintClaim = claims.FirstOrDefault(c => c.Type == "Fingerprint");
if (fingerprintClaim != null)
{
var hashedFingerprint = fingerprintClaim.Value;
_logger.LogDebug($"HashedFp: {hashedFingerprint} | Cookie Fp: {fingerprintClaim}");
// Verify the fingerprint here, based on how you generate the fingerprint hash
// If the fingerprint is valid, set the User property with the claims
if (FingerPrinter.ValidateFingerpint(fingerprint, hashedFingerprint))
{
// Record the claims so that they can be accessed by controllers
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.Select(c => c.Type == "Auth" ? new Claim(ClaimTypes.Role, c.Value) : c), "Jwt"));
}
}
// Validate signed headers
string payloadSig = context.Request.Headers["X-Payload-Signature"];
// Read the request body content
string payload;
using (var streamReader = new StreamReader(context.Request.Body))
{
payload = await streamReader.ReadToEndAsync();
}
string clientPublicKey = claims.FirstOrDefault(c => c.Type == "ClientPublicKey").Value;
_logger.LogDebug($"PayloadSig: {payloadSig} | payload: {payload} | Key: {clientPublicKey}");
//FIXME This could use some testing
try
{
bool validSig = KeyHelper.validateSignature(payloadSig, payload, clientPublicKey);
_logger.LogDebug($"Valid sig!");
if (!validSig)
{
_logger.LogWarning("Invalid signature!");
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Invalid signature!");
return;
}
}
catch
{
_logger.LogError($"Failed to verify signature!");
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
}
// This means the JWT is invalid
catch (SecurityTokenValidationException)
{
_logger.LogWarning("Invlaid JWT Exception!");
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Invalid token!");
return;
}
}
// Call the next middleware in the pipeline
await _next(context);
}
}