Skip to main content

JWTTokens

50. JWT Tokens​

Introduction​

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. JWTs are commonly used for authentication and information exchange in modern web applications and APIs.

Official Definition​

JWT is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed using a secret (HMAC) or a public/private key pair (RSA or ECDSA).

Usage & Setup​

# Install JWT packages
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.IdentityModel.Tokens
// Program.cs - JWT Configuration
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// JWT Configuration
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["SecretKey"];
var issuer = jwtSettings["Issuer"];
var audience = jwtSettings["Audience"];

builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ClockSkew = TimeSpan.Zero // Remove default 5-minute tolerance
};

options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

Use Cases​

  • Stateless API authentication
  • Single Sign-On (SSO) implementations
  • Microservices security
  • Mobile application authentication
  • Cross-domain authentication
  • Information exchange between services
  • Session management alternative

When to Use vs When Not to Use​

Use When:

  • Building stateless APIs
  • Implementing distributed systems
  • Need cross-domain authentication
  • Mobile/SPA applications
  • Microservices architecture
  • Performance is critical

Don't Use When:

  • Sensitive data needs to be stored
  • Tokens can't be revoked easily
  • Network security is poor
  • Simple session-based auth suffices
  • Real-time revocation is required
  • Token size becomes problematic

Market Alternatives & Adoption​

Alternatives:​

  • Session-based authentication
  • OAuth 2.0 access tokens
  • SAML tokens
  • API keys
  • Cookies with server sessions

Market Position: De facto standard for modern API authentication, adopted by major platforms and frameworks worldwide.

Pros and Cons​

Pros:​

  • Stateless and scalable
  • Self-contained with claims
  • Cross-domain support
  • JSON format (widely supported)
  • Compact size
  • Cryptographically signed

Cons:​

  • Cannot be revoked easily
  • Token size can grow large
  • Secret key management critical
  • No built-in refresh mechanism
  • Clock synchronization issues
  • Base64 encoding (not encryption)

Sample Implementation​

// Models/JwtModels.cs
public class JwtSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationMinutes { get; set; }
public int RefreshTokenExpirationDays { get; set; }
}

public class LoginRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
public string Password { get; set; }
}

public class AuthResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime Expiration { get; set; }
public UserInfo User { get; set; }
}

public class UserInfo
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> Roles { get; set; }
}

public class RefreshToken
{
public string Id { get; set; }
public string UserId { get; set; }
public string Token { get; set; }
public DateTime ExpiryDate { get; set; }
public bool IsRevoked { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedByIp { get; set; }
public DateTime? RevokedDate { get; set; }
public string RevokedByIp { get; set; }
public string ReplacedByToken { get; set; }
}

// Services/IJwtService.cs
public interface IJwtService
{
string GenerateAccessToken(ApplicationUser user, IList<string> roles);
RefreshToken GenerateRefreshToken(string userId, string ipAddress);
ClaimsPrincipal ValidateToken(string token);
Task<AuthResponse> RefreshTokenAsync(string token, string ipAddress);
Task RevokeTokenAsync(string token, string ipAddress);
Task<bool> IsTokenValidAsync(string token);
}

// Services/JwtService.cs
public class JwtService : IJwtService
{
private readonly JwtSettings _jwtSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
private readonly ILogger<JwtService> _logger;

public JwtService(
IOptions<JwtSettings> jwtSettings,
UserManager<ApplicationUser> userManager,
ApplicationDbContext context,
ILogger<JwtService> logger)
{
_jwtSettings = jwtSettings.Value;
_userManager = userManager;
_context = context;
_logger = logger;
}

public string GenerateAccessToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat,
new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64),
new Claim("firstName", user.FirstName ?? ""),
new Claim("lastName", user.LastName ?? ""),
new Claim("userId", user.Id)
};

// Add role claims
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}

public RefreshToken GenerateRefreshToken(string userId, string ipAddress)
{
using var rngCryptoServiceProvider = new RNGCryptoServiceProvider();
var randomBytes = new byte[64];
rngCryptoServiceProvider.GetBytes(randomBytes);

return new RefreshToken
{
Id = Guid.NewGuid().ToString(),
UserId = userId,
Token = Convert.ToBase64String(randomBytes),
ExpiryDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays),
CreatedDate = DateTime.UtcNow,
CreatedByIp = ipAddress
};
}

public ClaimsPrincipal ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey);

try
{
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = _jwtSettings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);

return principal;
}
catch
{
return null;
}
}

public async Task<AuthResponse> RefreshTokenAsync(string token, string ipAddress)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked);

if (refreshToken == null || refreshToken.ExpiryDate <= DateTime.UtcNow)
{
throw new SecurityTokenException("Invalid or expired refresh token");
}

var user = await _userManager.FindByIdAsync(refreshToken.UserId);
if (user == null)
{
throw new SecurityTokenException("User not found");
}

// Revoke current refresh token
refreshToken.IsRevoked = true;
refreshToken.RevokedDate = DateTime.UtcNow;
refreshToken.RevokedByIp = ipAddress;

// Generate new tokens
var roles = await _userManager.GetRolesAsync(user);
var newAccessToken = GenerateAccessToken(user, roles);
var newRefreshToken = GenerateRefreshToken(user.Id, ipAddress);

// Replace old token
refreshToken.ReplacedByToken = newRefreshToken.Token;

_context.RefreshTokens.Add(newRefreshToken);
await _context.SaveChangesAsync();

return new AuthResponse
{
AccessToken = newAccessToken,
RefreshToken = newRefreshToken.Token,
Expiration = DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),
User = new UserInfo
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = roles.ToList()
}
};
}

public async Task RevokeTokenAsync(string token, string ipAddress)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token);

if (refreshToken == null)
{
throw new SecurityTokenException("Token not found");
}

refreshToken.IsRevoked = true;
refreshToken.RevokedDate = DateTime.UtcNow;
refreshToken.RevokedByIp = ipAddress;

await _context.SaveChangesAsync();
}

public async Task<bool> IsTokenValidAsync(string token)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token);

return refreshToken != null &&
!refreshToken.IsRevoked &&
refreshToken.ExpiryDate > DateTime.UtcNow;
}
}

// Controllers/AuthController.cs
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IJwtService _jwtService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<AuthController> _logger;

public AuthController(
IJwtService jwtService,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<AuthController> logger)
{
_jwtService = jwtService;
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}

[HttpPost("login")]
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
return Unauthorized(new { message = "Invalid credentials" });
}

var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
return Unauthorized(new { message = "Invalid credentials" });
}

var roles = await _userManager.GetRolesAsync(user);
var accessToken = _jwtService.GenerateAccessToken(user, roles);
var refreshToken = _jwtService.GenerateRefreshToken(user.Id, GetIpAddress());

// Save refresh token to database
var context = HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
context.RefreshTokens.Add(refreshToken);
await context.SaveChangesAsync();

var response = new AuthResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken.Token,
Expiration = DateTime.UtcNow.AddMinutes(60), // Based on your JWT settings
User = new UserInfo
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = roles.ToList()
}
};

_logger.LogInformation("User {Email} logged in successfully", user.Email);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Login failed for {Email}", request.Email);
return StatusCode(500, new { message = "An error occurred during login" });
}
}

[HttpPost("refresh-token")]
public async Task<ActionResult<AuthResponse>> RefreshToken([FromBody] RefreshTokenRequest request)
{
try
{
var response = await _jwtService.RefreshTokenAsync(request.RefreshToken, GetIpAddress());
return Ok(response);
}
catch (SecurityTokenException ex)
{
return Unauthorized(new { message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Token refresh failed");
return StatusCode(500, new { message = "An error occurred during token refresh" });
}
}

[HttpPost("revoke-token")]
[Authorize]
public async Task<IActionResult> RevokeToken([FromBody] RevokeTokenRequest request)
{
try
{
await _jwtService.Revo// Models/JwtModels.cs
public class JwtSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationMinutes { get; set; }
public int RefreshTokenExpirationDays { get; set; }
}

public class LoginRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
public string Password { get; set; }
}

public class AuthResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime Expiration { get; set; }
public UserInfo User { get; set; }
}

public class UserInfo
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> Roles { get; set; }
}

public class RefreshToken
{
public string Id { get; set; }
public string UserId { get; set; }
public string Token { get; set; }
public DateTime ExpiryDate { get; set; }
public bool IsRevoked { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedByIp { get; set; }
public DateTime? RevokedDate { get; set; }
public string RevokedByIp { get; set; }
public string ReplacedByToken { get; set; }
}

// Services/IJwtService.cs
public interface IJwtService
{
string GenerateAccessToken(ApplicationUser user, IList<string> roles);
RefreshToken GenerateRefreshToken(string userId, string ipAddress);
ClaimsPrincipal ValidateToken(string token);
Task<AuthResponse> RefreshTokenAsync(string token, string ipAddress);
Task RevokeTokenAsync(string token, string ipAddress);
Task<bool> IsTokenValidAsync(string token);
}

// Services/JwtService.cs
public class JwtService : IJwtService
{
private readonly JwtSettings _jwtSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
private readonly ILogger<JwtService> _logger;

public JwtService(
IOptions<JwtSettings> jwtSettings,
UserManager<ApplicationUser> userManager,
ApplicationDbContext context,
ILogger<JwtService> logger)
{
_jwtSettings = jwtSettings.Value;
_userManager = userManager;
_context = context;
_logger = logger;
}

public string GenerateAccessToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat,
new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64),
new Claim("firstName", user.FirstName ?? ""),
new Claim("lastName", user.LastName ?? ""),
new Claim("userId", user.Id)
};

// Add role claims
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}

public RefreshToken GenerateRefreshToken(string userId, string ipAddress)
{
using var rngCryptoServiceProvider = new RNGCryptoServiceProvider();
var randomBytes = new byte[64];
rngCryptoServiceProvider.GetBytes(randomBytes);

return new RefreshToken
{
Id = Guid.NewGuid().ToString(),
UserId = userId,
Token = Convert.ToBase64String(randomBytes),
ExpiryDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays),
CreatedDate = DateTime.UtcNow,
CreatedByIp = ipAddress
};
}

public ClaimsPrincipal ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey);

try
{
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = _jwtSettings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);

return principal;
}
catch
{
return null;
}
}

public async Task<AuthResponse> RefreshTokenAsync(string token, string ipAddress)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked);

if (refreshToken == null || refreshToken.ExpiryDate <= DateTime.UtcNow)
{
throw new SecurityTokenException("Invalid or expired refresh token");
}

var user = await _userManager.FindByIdAsync(refreshToken.UserId);
if (user == null)
{
throw new SecurityTokenException("User not found");
}

// Revoke current refresh token
refreshToken.IsRevoked = true;
refreshToken.RevokedDate = DateTime.UtcNow;
refreshToken.RevokedByIp = ipAddress;

// Generate new tokens
var roles = await _userManager.GetRolesAsync(user);
var newAccessToken = GenerateAccessToken(user, roles);
var newRefreshToken = GenerateRefreshToken(user.Id, ipAddress);

// Replace old token
refreshToken.ReplacedByToken = newRefreshToken.Token;

_context.RefreshTokens.Add(newRefreshToken);
await _context.SaveChangesAsync();

return new AuthResponse
{
AccessToken = newAccessToken,
RefreshToken = newRefreshToken.Token,
Expiration = DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes),
User = new UserInfo
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = roles.ToList()
}
};
}

public async Task RevokeTokenAsync(string token, string ipAddress)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token);

if (refreshToken == null)
{
throw new SecurityTokenException("Token not found");
}

refreshToken.IsRevoked = true;
refreshToken.RevokedDate = DateTime.UtcNow;
refreshToken.RevokedByIp = ipAddress;

await _context.SaveChangesAsync();
}

public async Task<bool> IsTokenValidAsync(string token)
{
var refreshToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == token);

return refreshToken != null &&
!refreshToken.IsRevoked &&
refreshToken.ExpiryDate > DateTime.UtcNow;
}
}

// Controllers/AuthController.cs
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IJwtService _jwtService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<AuthController> _logger;

public AuthController(
IJwtService jwtService,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<AuthController> logger)
{
_jwtService = jwtService;
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}

[HttpPost("login")]
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
return Unauthorized(new { message = "Invalid credentials" });
}

var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
return Unauthorized(new { message = "Invalid credentials" });
}

var roles = await _userManager.GetRolesAsync(user);
var accessToken = _jwtService.GenerateAccessToken(user, roles);
var refreshToken = _jwtService.GenerateRefreshToken(user.Id, GetIpAddress());

// Save refresh token to database
var context = HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
context.RefreshTokens.Add(refreshToken);
await context.SaveChangesAsync();

var response = new AuthResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken.Token,
Expiration = DateTime.UtcNow.AddMinutes(60), // Based on your JWT settings
User = new UserInfo
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = roles.ToList()
}
};

_logger.LogInformation("User {Email} logged in successfully", user.Email);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Login failed for {Email}", request.Email);
return StatusCode(500, new { message = "An error occurred during login" });
}
}

[HttpPost("refresh-token")]
public async Task<ActionResult<AuthResponse>> RefreshToken([FromBody] RefreshTokenRequest request)
{
try
{
var response = await _jwtService.RefreshTokenAsync(request.RefreshToken, GetIpAddress());
return Ok(response);
}
catch (SecurityTokenException ex)
{
return Unauthorized(new { message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Token refresh failed");
return StatusCode(500, new { message = "An error occurred during token refresh" });
}
}

[HttpPost("revoke-token")]
[Authorize]
public async Task<IActionResult> RevokeToken([FromBody] RevokeTokenRequest request)
{
try
{
await _jwtService.Revo

Similar code found with 4 license types

sreee2001: please continue from after

[HttpPost("refresh-token")]
public async Task<ActionResult<AuthResponse>> RefreshToken([FromBody] RefreshTokenRequest request)
{
try
{
var response = await _jwtService.RefreshTokenAsync(request.RefreshToken, GetIpAddress());
return Ok(response);
}
catch (SecurityTokenException ex)
{
return Unauthorized(new { message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Token refresh failed");
return StatusCode(500, new { message = "An error occurred during token refresh" });
}
}

[HttpPost("revoke-token")]
[Authorize]
public async Task<IActionResult> RevokeToken([FromBody] RevokeTokenRequest request)
{
try
{
await _jwtService.Revo

------------
if needed please start from after stable sub section. You were in the middle of Sample Implementation of 50. JWT Tokens
- if you think you will run out of response limit. pause at a stable point and print --continue-- and keep the state in mind , so when I ask you to continue, you can continue the task from that checkpoint

GitHub Copilot: I'll continue from where the JWT Tokens sample implementation left off, completing the controller and then moving to the remaining sections.

## 50. JWT Tokens (continued)

```csharp
[HttpPost("revoke-token")]
[Authorize]
public async Task<IActionResult> RevokeToken([FromBody] RevokeTokenRequest request)
{
try
{
await _jwtService.RevokeTokenAsync(request.RefreshToken);
return Ok(new { message = "Token revoked successfully" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Token revocation failed");
return BadRequest(new { message = "Token revocation failed" });
}
}

private string GetIpAddress()
{
return HttpContext.Connection.RemoteIpAddress?.MapToIPv4().ToString() ?? "Unknown";
}
}

// Models/DTOs
public class LoginRequest
{
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}

public class AuthResponse
{
public string AccessToken { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
public DateTime ExpiresAt { get; set; }
public UserDto User { get; set; } = null!;
}

public class RefreshTokenRequest
{
public string RefreshToken { get; set; } = string.Empty;
}

public class RevokeTokenRequest
{
public string RefreshToken { get; set; } = string.Empty;
}

// JWT Configuration in appsettings.json
{
"JwtSettings": {
"SecretKey": "your-super-secret-key-that-is-at-least-256-bits-long",
"Issuer": "HotelManagement.Api",
"Audience": "HotelManagement.Client",
"ExpirationInMinutes": 15,
"RefreshTokenExpirationInDays": 7
}
}

// Program.cs configuration
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var jwtSettings = builder.Configuration.GetSection("JwtSettings").Get<JwtSettings>();
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
ValidateIssuer = true,
ValidIssuer = jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = jwtSettings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});

NuGet Packages Required:

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />