Skip to content

Instantly share code, notes, and snippets.

@fakhrulhilal
Created June 13, 2023 08:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fakhrulhilal/dd32ad2c51d23ca150ad293243a2fd6c to your computer and use it in GitHub Desktop.
Save fakhrulhilal/dd32ad2c51d23ca150ad293243a2fd6c to your computer and use it in GitHub Desktop.
ASP.NET core middleware to block JWT token containing blacklisted JTI
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.IdentityModel.Tokens;
/// <summary>
/// Reject JWT token containing blacklisted JTI. The JTI is registered into cache provider to be blacklisted,
/// f.e. during logging out.
/// Usage:
/// <example>
/// app.UseMiddleware&lt;JwtSessionMiddleware&gt;();
/// </example>
/// </summary>
public sealed class JwtSessionMiddleware
{
private const string UserIdToken = "MyUserIdFromJwtToken";
private readonly RequestDelegate _next;
private readonly IDistributedCache _cacheService;
public JwtSessionMiddleware(RequestDelegate next, IDistributedCache cacheService) {
_cacheService = cacheService;
_next = next;
}
public async Task InvokeAsync(HttpContext context) {
if (context.User.Identity is not ClaimsIdentity { IsAuthenticated: true } identity) {
await _next(context);
return;
}
int userId = identity.FindFirst(UserIdToken) is { } claim &&
int.TryParse(claim.Value, out int id)
? id
: default;
string sessionId = identity.FindFirst(JwtRegisteredClaimNames.Jti)?.Value ?? string.Empty;
bool isValid = await IsValidSession(userId, sessionId);
if (!isValid) {
throw new SecurityTokenExpiredException("Invalid token specified.");
}
await _next(context);
}
private async Task<bool> IsValidSession(int userId, string sessionId) {
if (string.IsNullOrWhiteSpace(sessionId) || userId == default) {
return false;
}
var sessions = await GetBlacklistedSessions(userId);
bool isBlackListed = sessions.Any(t => t.SessionId == sessionId);
return !isBlackListed;
}
private async Task<CachedIdentityToken[]> GetBlacklistedSessions(int userId) {
string cacheKey = $"{userId}";
string json = await _cacheService.GetStringAsync(cacheKey) ?? string.Empty;
return string.IsNullOrEmpty(json)
? Array.Empty<CachedIdentityToken>()
: JsonSerializer.Deserialize<CachedIdentityToken[]>(json) ?? Array.Empty<CachedIdentityToken>();
}
private sealed class CachedIdentityToken
{
[JsonPropertyName("JWTId")]
public string SessionId { get; set; } = string.Empty;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment