Skip to content

Commit 76fcfbf

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

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;
@@ -10,6 +9,10 @@
109
using System.Text;
1110
using Swashbuckle.AspNetCore.Filters;
1211
using OsmoDoc.Pdf;
12+
using StackExchange.Redis;
13+
using OsmoDoc.API.Models;
14+
using OsmoDoc.Services;
15+
using System.IdentityModel.Tokens.Jwt;
1316

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

@@ -32,6 +35,12 @@
3235
builder.Configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value!
3336
);
3437

38+
// Register REDIS service
39+
builder.Services.AddSingleton<IConnectionMultiplexer>(
40+
ConnectionMultiplexer.Connect(Environment.GetEnvironmentVariable("REDIS_URL") ?? throw new Exception("No REDIS URL specified"))
41+
);
42+
builder.Services.AddScoped<IRedisTokenStoreService, RedisTokenStoreService>();
43+
3544
// Configure request size limit
3645
long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value);
3746

@@ -101,6 +110,21 @@
101110
return true;
102111
}
103112
};
113+
114+
options.Events = new JwtBearerEvents
115+
{
116+
OnTokenValidated = async context =>
117+
{
118+
IRedisTokenStoreService tokenStore = context.HttpContext.RequestServices.GetRequiredService<IRedisTokenStoreService>();
119+
JwtSecurityToken? token = context.SecurityToken as JwtSecurityToken;
120+
string tokenString = context.Request.Headers["Authorization"].ToString().Replace("bearer ", "");
121+
122+
if (!await tokenStore.IsTokenValidAsync(tokenString))
123+
{
124+
context.Fail("Token has been revoked.");
125+
}
126+
}
127+
};
104128
});
105129

106130
// 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)