- 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.
139 lines
5.3 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
} |