using CORE.APP.Models.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace CORE.APP.Services.Authentication
{
/// <summary>
/// Inherits from the base AuthServiceBase class to use getting claims of a user operation and implements ITokenAuthService interface.
/// Provides concrete implementations for token-based authentication operations, including
/// generating token response with JWT (access token) and refresh token, generating refresh token, and extracting claims from access token (JWT).
/// This service is responsible for securely creating and validating JWT used in authentication flows.
/// </summary>
public class TokenAuthService : AuthServiceBase, ITokenAuthService
{
/// <summary>
/// Returns a token response including JWT (access token) and refresh token.
/// </summary>
/// <param name="userId">The unique identifier of the user.</param>
/// <param name="userName">The username of the user.</param>
/// <param name="userRoleNames">An array of role names assigned to the user.</param>
/// <param name="expiration">The expiration date and time for the JWT.</param>
/// <param name="securityKey">The security key used to sign the JWT.</param>
/// <param name="issuer">The issuer of the JWT, generally the server API application's domain.</param>
/// <param name="audience">The intended audience for the JWT, generally the client application's domain.</param>
/// <param name="refreshToken">Refresh token to be included in the returned <see cref="TokenResponse"/> object.</param>
/// <returns>A <see cref="TokenResponse"/> object containing the created JWT and provided refresh token.</returns>
public TokenResponse GetTokenResponse(int userId, string userName, string[] userRoleNames, DateTime expiration,
string securityKey, string issuer, string audience, string refreshToken)
{
// Generate claims.
var claims = GetClaims(userId, userName, userRoleNames);
// Create signing credentials using the provided security key.
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
// Build the JWT with claims, issuer, audience, and expiration.
var jwtSecurityToken = new JwtSecurityToken(issuer, audience, claims, DateTime.Now, expiration, signingCredentials);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
// Serialize the JWT to a string.
var jwt = jwtSecurityTokenHandler.WriteToken(jwtSecurityToken);
// Return the token response with the serialized JWT value and the refresh token parameter value.
return new TokenResponse()
{
Token = $"{JwtBearerDefaults.AuthenticationScheme} {jwt}", // JwtBearerDefaults.AuthenticationScheme: "Bearer"
RefreshToken = refreshToken
};
}
/// <summary>
/// Generates a new refresh token, which is a secure, random string used to obtain new JWT.
/// </summary>
/// <returns>A string representing the newly generated refresh token.</returns>
public string GetRefreshToken()
{
// Generate a cryptographically secure random 32-byte refresh token.
var bytes = new byte[32];
using (var randomNumberGenerator = RandomNumberGenerator.Create())
{
randomNumberGenerator.GetBytes(bytes);
}
return Convert.ToBase64String(bytes);
}
/// <summary>
/// Extracts and returns a collection of claims from the specified access token (JWT) using the provided security key.
/// </summary>
/// <param name="token">The JWT containing encoded claims.</param>
/// <param name="securityKey">The security key used to validate and decode the JWT.</param>
/// <returns>An <see cref="IEnumerable{Claim}"/> containing the claims extracted from the JWT.</returns>
public IEnumerable<Claim> GetClaims(string token, string securityKey)
{
// IEnumerable is an interface that the List class implements. LINQ methods can also be used with IEnumerable.
// An IEnumerable collection can be converted to a List collection by invoking ToList method when needed.
// Remove the "Bearer" prefix if exists in the JWT.
token = token.StartsWith(JwtBearerDefaults.AuthenticationScheme) ?
token.Remove(0, JwtBearerDefaults.AuthenticationScheme.Length + 1) : token;
// Prepare the signing key and validation parameters.
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey
};
// Validate the JWT and extract claims.
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
return securityToken is null ? null : principal.Claims;
}
}
}