Skip to content

Commit 76e0ab1

Browse files
committed
feat: implement RedisTokenService for JWT token storage and validation
1 parent 9044885 commit 76e0ab1

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

OsmoDoc.API/Program.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using OsmoDoc.API.Models;
21
using Microsoft.AspNetCore.Mvc;
32
using Serilog.Events;
43
using Serilog;
@@ -9,6 +8,10 @@
98
using Microsoft.IdentityModel.Tokens;
109
using System.Text;
1110
using Swashbuckle.AspNetCore.Filters;
11+
using StackExchange.Redis;
12+
using OsmoDoc.API.Models;
13+
using OsmoDoc.Services;
14+
using System.IdentityModel.Tokens.Jwt;
1215

1316
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
1417

@@ -25,6 +28,12 @@
2528
string dotenv = Path.GetFullPath(Path.Combine(root, "..", ".env"));
2629
OsmoDoc.API.DotEnv.Load(dotenv);
2730

31+
// Register REDIS service
32+
builder.Services.AddSingleton<IConnectionMultiplexer>(
33+
ConnectionMultiplexer.Connect(Environment.GetEnvironmentVariable("REDIS_URL") ?? throw new Exception("No REDIS URL specified"))
34+
);
35+
builder.Services.AddScoped<IRedisTokenStoreService, RedisTokenStoreService>();
36+
2837
// Configure request size limit
2938
long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value);
3039

@@ -94,6 +103,21 @@
94103
return true;
95104
}
96105
};
106+
107+
options.Events = new JwtBearerEvents
108+
{
109+
OnTokenValidated = async context =>
110+
{
111+
IRedisTokenStoreService tokenStore = context.HttpContext.RequestServices.GetRequiredService<IRedisTokenStoreService>();
112+
JwtSecurityToken? token = context.SecurityToken as JwtSecurityToken;
113+
string tokenString = context.Request.Headers["Authorization"].ToString().Replace("bearer ", "");
114+
115+
if (!await tokenStore.IsTokenValidAsync(tokenString))
116+
{
117+
context.Fail("Token has been revoked.");
118+
}
119+
}
120+
};
97121
});
98122

99123
// Configure Error Response from Model Validations
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
3+
namespace OsmoDoc.Services;
4+
5+
public interface IRedisTokenStoreService
6+
{
7+
Task StoreTokenAsync(string token, string email);
8+
Task<bool> IsTokenValidAsync(string token);
9+
Task RevokeTokenAsync(string token);
10+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Newtonsoft.Json;
4+
using StackExchange.Redis;
5+
6+
namespace OsmoDoc.Services;
7+
8+
public class RedisTokenStoreService : IRedisTokenStoreService
9+
{
10+
private readonly IDatabase _db;
11+
private const string KeyPrefix = "valid_token:";
12+
13+
public RedisTokenStoreService(IConnectionMultiplexer redis)
14+
{
15+
this._db = redis.GetDatabase();
16+
}
17+
18+
public Task StoreTokenAsync(string token, string email)
19+
{
20+
return this._db.StringSetAsync($"{KeyPrefix}{token}", JsonConvert.SerializeObject(new {
21+
issuedTo = email,
22+
issuedAt = DateTime.UtcNow
23+
}));
24+
}
25+
26+
public Task<bool> IsTokenValidAsync(string token)
27+
{
28+
return this._db.KeyExistsAsync($"{KeyPrefix}{token}");
29+
}
30+
31+
public Task RevokeTokenAsync(string token)
32+
{
33+
return this._db.KeyDeleteAsync($"{KeyPrefix}{token}");
34+
}
35+
}

0 commit comments

Comments
 (0)