Created
June 13, 2023 08:52
-
-
Save fakhrulhilal/dd32ad2c51d23ca150ad293243a2fd6c to your computer and use it in GitHub Desktop.
ASP.NET core middleware to block JWT token containing blacklisted JTI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<JwtSessionMiddleware>(); | |
/// </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