Quite some time ago I published an article (along with the source code) about refreshing the JWTtokens. In the following post, I’m going to focus on canceling the token, thus it can’t be used by anyone else. This tutorial includes the video, so it might be easier to understand the implementation flow.
Given that we do not make use of OAuth (IdentityServer etc.) what can we do in terms of canceling the active tokens? We have a few options:
- Remove token on the client side (e.g. local storage) – will do the trick, but doesn’t really cancel the token.
- Keep the token lifetime relatively short (5 minutes or so) – most likely we should do it anyway.
- Create a blacklist of tokens that were deactivated – this is what we are going to focus on.
The important note is that in order to make it reliable we will use the Redis to store the deactivated tokens on an extremely fast caching server. Whether you host just a single instance of your application or multiple ones, it’s the best idea to use Redis – otherwise, when server goes down, you will lose all of the deactivated tokens blacklist being kept in a default server cache (not to mention the different data if each server would keep its own cache).
Alright, no more theory, proceed with coding, where we will start with the interface:
1 2 3 4 5 6 7 |
public interface ITokenManager { Task<bool> IsCurrentActiveToken(); Task DeactivateCurrentAsync(); Task<bool> IsActiveAsync(string token); Task DeactivateAsync(string token); } |
And process with its implementation, where the basic idea is to keep track of deactivated tokens only and remove them from a cache when not needed anymore (meaning when the expiry time passed) – they will be no longer valid anyway.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public class TokenManager : ITokenManager { private readonly IDistributedCache _cache; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IOptions<JwtOptions> _jwtOptions; public TokenManager(IDistributedCache cache, IHttpContextAccessor httpContextAccessor, IOptions<JwtOptions> jwtOptions ) { _cache = cache; _httpContextAccessor = httpContextAccessor; _jwtOptions = jwtOptions; } public async Task<bool> IsCurrentActiveToken() => await IsActiveAsync(GetCurrentAsync()); public async Task DeactivateCurrentAsync() => await DeactivateAsync(GetCurrentAsync()); public async Task<bool> IsActiveAsync(string token) => await _cache.GetStringAsync(GetKey(token)) == null; public async Task DeactivateAsync(string token) => await _cache.SetStringAsync(GetKey(token), " ", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_jwtOptions.Value.ExpiryMinutes) }); private string GetCurrentAsync() { var authorizationHeader = _httpContextAccessor .HttpContext.Request.Headers["authorization"]; return authorizationHeader == StringValues.Empty ? string.Empty : authorizationHeader.Single().Split(" ").Last(); } private static string GetKey(string token) => $"tokens:{token}:deactivated"; } |
As you can see, there are 2 helper methods that will use the current HttpContext in order to make things even easier.
Next, let’s create a middleware that will check if the token was deactivated or not. That’s the reason why we should keep them in cache – hitting the database with every request instead would probably kill your app sooner or later (or at least make it really, really slow):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class TokenManagerMiddleware : IMiddleware { private readonly ITokenManager _tokenManager; public TokenManagerMiddleware(ITokenManager tokenManager) { _tokenManager = tokenManager; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (await _tokenManager.IsCurrentActiveToken()) { await next(context); return; } context.Response.StatusCode = (int) HttpStatusCode.Unauthorized; } } |
Eventually, let’s finish our journey with implementing an endpoint for canceling the tokens:
1 2 3 4 5 6 7 |
[HttpPost("tokens/cancel")] public async Task<IActionResult> CancelAccessToken() { await _tokenManager.DeactivateCurrentAsync(); return NoContent(); } |
For sure, we could make it more sophisticated, via passing the token via URL, or by canceling all of the existing user tokens at once (which would require an additional implementation to keep track of them), yet this is a basic sample that just works.
Make sure that you will register the required dependencies in your container and configure the middleware:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public void ConfigureServices(IServiceCollection services) { ... services.AddTransient<TokenManagerMiddleware>(); services.AddTransient<ITokenManager, Services.TokenManager>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddDistributedRedisCache(r => { r.Configuration = Configuration["redis:connectionString"]; ... } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { ... app.UseAuthentication(); app.UseMiddleware<TokenManagerMiddleware>(); app.UseMvc(); } |
And provide a configuration for Redis in appsettings.json file:
1 2 3 |
"redis": { "connectionString": "localhost" } |
Try to run the application now and invoke the token cancelation endpoint – that’s it.
Source code is available here.
Pingback: Canceling JWT tokens in .NET Core - How to Code .NET
Norton Utilities can be broadly defined as a utility software suite, which is designed to provide complete assistance for analyzing, configuring, optimizing and maintaining a system. While downloading, installing or configuring the antivirus on your device, you may face an error. If it happens to you, don’t get panic and call our Norton.com/Setup number 1-888-406-4114. Our technicians will provide you an instant support for Norton Product.
Why Choose us:
– Certified Technicians
– 24/7 Availability
– Best Services
– Prompt Delivery
?
My app return this message :”It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail. SocketFailure on PING” what can i do??
First of all, the article is great.
What cannot I understand is.. how can I cancel token for the specified user? When I know the user Id but I don’t know his token?
Really useful information. Thank you so much for sharing.It will help everyone.Keep Post. RPA training in chennai | RPA training in Chennai with placement
Outstanding blog thanks for sharing such wonderful blog with us ,after long time came across such knowlegeble blog. keep sharing such informative blog with us.
Nice blog..! I really loved reading through this article. Thanks for sharing such a amazing post with us and keep blogging… angular 4 training in chennai | angularjs training in omr | best angularjs training institute in chennai | angularjs training in omr
I really happy found this website eventually. Really informative and inoperative, Thanks for the post and effort! Please keep sharing more such blog.norton.com/setup
I really happy found this website eventually. Really informative and inoperative, Thanks for the post and effort! Please keep sharing more such blog. Norton.com/nu16
Thank you for posting such a great article! I found your site perfect for my needs. It contains interesting and useful posts for me. Please continue to uphold. Thank you for this great article.