157 lines
5.7 KiB
C#
157 lines
5.7 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Marco.Pms.DataAccess.Data;
|
|
using Marco.Pms.Model.Authentication;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
#nullable disable
|
|
namespace MarcoBMS.Services.Service
|
|
{
|
|
public class RefreshTokenService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
private readonly IMemoryCache _cache; // For optional JWT blacklisting
|
|
private readonly ILoggingService _logger;
|
|
|
|
|
|
public RefreshTokenService(ApplicationDbContext context, IMemoryCache cache, ILoggingService logger)
|
|
{
|
|
_context = context;
|
|
_cache = cache;
|
|
_logger = logger;
|
|
}
|
|
|
|
public string GenerateJwtToken(string username, string tenantId, JwtSettings _jwtSettings)
|
|
{
|
|
|
|
// Custom claims
|
|
var claims = new List<Claim>
|
|
{
|
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
|
new Claim(JwtRegisteredClaimNames.Sub, username),
|
|
new Claim("TenantId", tenantId), // Add TenantId claim
|
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) };
|
|
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: _jwtSettings.Issuer,
|
|
audience: _jwtSettings.Audience,
|
|
claims: claims,
|
|
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiresInMinutes),
|
|
signingCredentials: creds);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
|
|
public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings _jwtSettings)
|
|
{
|
|
try
|
|
{
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
var key = Encoding.UTF8.GetBytes(_jwtSettings.Key);
|
|
|
|
var tokenDescriptor = new SecurityTokenDescriptor
|
|
{
|
|
Subject = new ClaimsIdentity(new[]
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, userId),
|
|
new Claim("TenantId", tenantId), // Add TenantId claim
|
|
|
|
new Claim("token_type", "refresh") // Custom claim to differentiate refresh tokens
|
|
}),
|
|
Expires = DateTime.UtcNow.AddDays(7), // Refresh token valid for 7 days
|
|
Issuer = _jwtSettings.Issuer,
|
|
Audience = _jwtSettings.Audience,
|
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
|
};
|
|
|
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
|
string strToken = tokenHandler.WriteToken(token);
|
|
|
|
var refreshToken = new RefreshToken
|
|
{
|
|
Token = strToken,
|
|
UserId = userId,
|
|
ExpiryDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiresInDays),
|
|
IsRevoked = false
|
|
};
|
|
|
|
// Check if the record exists
|
|
var existingEntity = await _context.RefreshTokens.AnyAsync(c=>c.UserId == userId);
|
|
if(existingEntity) { _context.RefreshTokens.Update(refreshToken); } else
|
|
{
|
|
_context.RefreshTokens.Add(refreshToken);
|
|
}
|
|
await _context.SaveChangesAsync();
|
|
return strToken;
|
|
}catch(Exception ex)
|
|
{
|
|
_logger.LogError("{Error}", ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<RefreshToken> GetRefreshToken(string token)
|
|
{
|
|
return await _context.RefreshTokens.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked && !rt.IsUsed) ?? new RefreshToken();
|
|
}
|
|
|
|
public async Task MarkRefreshTokenAsUsed(RefreshToken refreshToken)
|
|
{
|
|
refreshToken.IsUsed = true;
|
|
_context.RefreshTokens.Update(refreshToken);
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task RevokeRefreshToken(RefreshToken refreshToken)
|
|
{
|
|
refreshToken.IsRevoked = true;
|
|
refreshToken.RevokedAt = DateTime.UtcNow;
|
|
|
|
_context.RefreshTokens.Update(refreshToken);
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
|
|
// Revoke refresh token
|
|
public async Task<bool> RevokeRefreshTokenAsync(string refreshToken)
|
|
{
|
|
var token = await _context.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken);
|
|
|
|
if (token == null || token.IsRevoked || token.ExpiryDate <= DateTime.UtcNow)
|
|
return false;
|
|
|
|
token.IsRevoked = true;
|
|
token.RevokedAt = DateTime.UtcNow;
|
|
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
// Optional: Blacklist JWT token
|
|
public Task BlacklistJwtTokenAsync(string jwtToken)
|
|
{
|
|
// Store the JWT token in memory cache with its expiry
|
|
var jwtExpiry = GetJwtExpiry(jwtToken);
|
|
if (jwtExpiry.HasValue)
|
|
{
|
|
_cache.Set(jwtToken, true, jwtExpiry.Value - DateTime.UtcNow);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private DateTime? GetJwtExpiry(string token)
|
|
{
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var jwtToken = handler.ReadToken(token) as JwtSecurityToken;
|
|
return jwtToken?.ValidTo;
|
|
}
|
|
|
|
}
|
|
}
|