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.
This commit is contained in:
139
Api/Services/JwtService.cs
Normal file
139
Api/Services/JwtService.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user