Files
pas/Api/Services/JwtService.cs
Marek Lesko f34d523413 feat: Implement OAuth2 authentication with Microsoft, Google, and PocketId
- Added JWT configuration to appsettings.json for secure token handling.
- Updated config.json to include OAuth provider details for Microsoft, Google, and PocketId.
- Added Microsoft icon SVG for UI representation.
- Refactored app.config.ts to use a custom AuthInterceptor for managing access tokens.
- Enhanced auth route guard to handle asynchronous authentication checks.
- Created new auth models for structured request and response handling.
- Developed a callback component to manage user login states and transitions.
- Updated side-login component to support multiple OAuth providers with loading states.
- Implemented authentication service methods for handling OAuth login flows and token management.
- Added error handling and user feedback for authentication processes.
2025-11-07 19:23:21 +00:00

139 lines
5.3 KiB
C#

using Api.Models;
using Api.Models.DTOs;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Api.Services
{
public interface IJwtService
{
string GenerateToken(User user);
ClaimsPrincipal? ValidateToken(string token);
DateTime GetTokenExpiration(string token);
}
public class JwtService : IJwtService
{
private readonly JwtSettings _jwtSettings;
private readonly ILogger<JwtService> _logger;
private readonly SymmetricSecurityKey _key;
public JwtService(IConfiguration configuration, ILogger<JwtService> logger)
{
_jwtSettings = configuration.GetSection("Jwt").Get<JwtSettings>()
?? throw new InvalidOperationException("JWT settings not configured");
_logger = logger;
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
}
public string GenerateToken(User user)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var credentials = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Email, user.Email),
new Claim("jti", Guid.NewGuid().ToString()),
new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
// Add optional claims if available
if (!string.IsNullOrEmpty(user.FirstName))
{
claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName));
}
if (!string.IsNullOrEmpty(user.LastName))
{
claims.Add(new Claim(ClaimTypes.Surname, user.LastName));
}
// Add provider information
var providers = user.OAuthProviders.Select(p => p.Provider.ToString()).ToList();
if (providers.Any())
{
claims.Add(new Claim("providers", string.Join(",", providers)));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),
SigningCredentials = credentials,
Issuer = _jwtSettings.Issuer,
Audience = _jwtSettings.Audience
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating JWT token for user {UserId}", user.Id);
throw new InvalidOperationException("Failed to generate access token", ex);
}
}
public ClaimsPrincipal? ValidateToken(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = _key,
ValidateIssuer = true,
ValidIssuer = _jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = _jwtSettings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5) // Allow 5 minutes clock skew
};
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
// Ensure the token is a JWT token with the correct algorithm
if (validatedToken is not JwtSecurityToken jwtToken ||
!jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
return principal;
}
catch (SecurityTokenException ex)
{
_logger.LogWarning(ex, "Invalid token validation attempt");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating JWT token");
return null;
}
}
public DateTime GetTokenExpiration(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jsonToken = tokenHandler.ReadJwtToken(token);
return jsonToken.ValidTo;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading token expiration");
throw new InvalidOperationException("Invalid token format", ex);
}
}
}
}