Skip to content

Commit 8a95a31

Browse files
Enhances JWT refresh token handling
Improves the security and reliability of refresh token functionality by using in-memory caching to validate refresh tokens. Changes refresh token expiry to minutes, for better configurability. This ensures that only valid refresh tokens can be used to obtain new access tokens.
1 parent db40992 commit 8a95a31

File tree

4 files changed

+69
-41
lines changed

4 files changed

+69
-41
lines changed

src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/AuthController.cs

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
using System.Net.Http.Headers;
2-
using System.Security.Claims;
3-
using Certify.Client;
1+
using Certify.Client;
42
using Certify.Models.Hub;
53
using Microsoft.AspNetCore.Authentication.JwtBearer;
64
using Microsoft.AspNetCore.Authorization;
75
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Extensions.Caching.Memory;
87

98
namespace Certify.Server.Hub.Api.Controllers
109
{
@@ -19,17 +18,22 @@ public partial class AuthController : ApiControllerBase
1918
private readonly ICertifyInternalApiClient _client;
2019
private IConfiguration _config;
2120

21+
private readonly IMemoryCache _memoryCache;
22+
2223
/// <summary>
2324
/// Controller for Auth operations
2425
/// </summary>
2526
/// <param name="logger"></param>
2627
/// <param name="client"></param>
2728
/// <param name="config"></param>
28-
public AuthController(ILogger<AuthController> logger, ICertifyInternalApiClient client, IConfiguration config)
29+
/// <param name="memoryCache"></param>
30+
public AuthController(ILogger<AuthController> logger, ICertifyInternalApiClient client, IConfiguration config, IMemoryCache memoryCache)
2931
{
3032
_logger = logger;
3133
_client = client;
3234
_config = config;
35+
36+
_memoryCache = memoryCache;
3337
}
3438

3539
/// <summary>
@@ -44,6 +48,15 @@ public async Task<IActionResult> CheckAuthStatus()
4448
return await Task.FromResult(new OkResult());
4549
}
4650

51+
private void CacheRefreshToken(string userId, string refreshToken)
52+
{
53+
var refreshTokenExpiryMinutes = int.Parse(_config["JwtSettings:refreshTokenExpirationInMinutes"] ?? "600");
54+
55+
var expiry = new TimeSpan(0, refreshTokenExpiryMinutes, 0);
56+
57+
_memoryCache.Set("RefreshToken_" + refreshToken, userId, expiry);
58+
}
59+
4760
/// <summary>
4861
/// Perform login using username and password
4962
/// </summary>
@@ -65,13 +78,26 @@ public async Task<IActionResult> Login(AuthRequest login)
6578

6679
var jwt = new Hub.Api.Services.JwtService(_config);
6780

81+
var refreshToken = jwt.GenerateRefreshToken();
82+
83+
CacheRefreshToken(validation.SecurityPrincipal.Id, refreshToken);
84+
85+
var jwtExpiryMinutes = double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20");
86+
var newJwt = jwt.GenerateSecurityToken(validation.SecurityPrincipal.Id, jwtExpiryMinutes);
87+
88+
var authContext = new AuthContext
89+
{
90+
UserId = validation.SecurityPrincipal.Id,
91+
Token = newJwt
92+
};
93+
6894
var authResponse = new AuthResponse
6995
{
7096
Detail = "OK",
71-
AccessToken = jwt.GenerateSecurityToken(validation.SecurityPrincipal.Id, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20")),
72-
RefreshToken = jwt.GenerateRefreshToken(),
97+
AccessToken = newJwt,
98+
RefreshToken = refreshToken,
7399
SecurityPrincipal = validation.SecurityPrincipal,
74-
RoleStatus = await _client.GetSecurityPrincipalRoleStatus(validation.SecurityPrincipal.Id, CurrentAuthContext)
100+
RoleStatus = await _client.GetSecurityPrincipalRoleStatus(validation.SecurityPrincipal.Id, authContext)
75101
};
76102

77103
// TODO: Refresh token should be stored or hashed for later use
@@ -91,56 +117,57 @@ public async Task<IActionResult> Login(AuthRequest login)
91117
}
92118

93119
/// <summary>
94-
/// Refresh users current auth token
120+
/// Refresh users current auth token using refresh token
95121
/// </summary>
96122
/// <param name="refreshToken"></param>
97123
/// <returns></returns>
98-
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
124+
[AllowAnonymous]
99125
[HttpPost]
100126
[Route("refresh")]
101127
[ProducesResponseType(typeof(AuthResponse), StatusCodes.Status200OK)]
102128
public async Task<IActionResult> Refresh(string refreshToken)
103129
{
104-
var authToken = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]!).Parameter;
105-
106-
if (string.IsNullOrEmpty(authToken))
107-
{
108-
return Unauthorized();
109-
}
110-
111130
try
112131
{
113132
// validate token and issue new one
114-
var jwt = new Hub.Api.Services.JwtService(_config);
133+
if (_memoryCache.TryGetValue("RefreshToken_" + refreshToken, out string? userId))
134+
{
135+
// we have a valid refresh token, refresh and auth user
115136

116-
var claimsIdentity = await jwt.ClaimsIdentityFromTokenAsync(authToken, false);
117-
var userId = claimsIdentity.FindFirst(ClaimTypes.Sid)?.Value;
137+
var spList = await _client.GetSecurityPrincipals(CurrentAuthContext);
138+
var sp = spList.Single(s => s.Id == userId);
118139

119-
if (userId == null)
120-
{
121-
return Unauthorized();
122-
}
140+
var jwtExpiryMinutes = double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20");
141+
var jwt = new Hub.Api.Services.JwtService(_config);
142+
var newJwt = jwt.GenerateSecurityToken(sp.Id, jwtExpiryMinutes);
123143

124-
var newJwtToken = jwt.GenerateSecurityToken(userId, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20"));
125-
var newRefreshToken = jwt.GenerateRefreshToken();
144+
// invalidate old refresh token and store new one
145+
var newRefreshToken = jwt.GenerateRefreshToken();
126146

127-
// invalidate old refresh token and store new one
128-
// DeleteRefreshToken(username, refreshToken);
129-
// SaveRefreshToken(username, newRefreshToken);
147+
CacheRefreshToken(sp.Id, newRefreshToken);
130148

131-
var spList = await _client.GetSecurityPrincipals(CurrentAuthContext);
132-
var sp = spList.Single(s => s.Id == userId);
149+
var authContext = new AuthContext
150+
{
151+
UserId = sp.Id,
152+
Token = newJwt
153+
};
133154

134-
var authResponse = new AuthResponse
135-
{
136-
Detail = "OK",
137-
AccessToken = newJwtToken,
138-
RefreshToken = newRefreshToken,
139-
SecurityPrincipal = sp,
140-
RoleStatus = await _client.GetSecurityPrincipalRoleStatus(userId, CurrentAuthContext)
141-
};
155+
var authResponse = new AuthResponse
156+
{
157+
Detail = "OK",
158+
AccessToken = newJwt,
159+
RefreshToken = newRefreshToken,
160+
SecurityPrincipal = sp,
161+
RoleStatus = await _client.GetSecurityPrincipalRoleStatus(sp.Id, authContext)
162+
};
142163

143-
return Ok(authResponse);
164+
return Ok(authResponse);
165+
}
166+
else
167+
{
168+
// no valid refresh token found
169+
return Unauthorized();
170+
}
144171
}
145172
catch
146173
{

src/Certify.Server/Certify.Server.Hub.Api/Services/JwtService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public JwtService(IConfiguration config)
2727
_secret = config.GetSection("JwtSettings").GetSection("secret").Value ?? "";
2828
_issuer = config.GetSection("JwtSettings").GetSection("issuer").Value ?? "";
2929
_expDate = config.GetSection("JwtSettings").GetSection("expirationInDays").Value ?? "1";
30+
3031
}
3132

3233
/// <summary>

src/Certify.Server/Certify.Server.Hub.Api/appsettings-api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"AllowedHosts": "*",
1010
"JwtSettings": {
1111
"secret": "8FdYdFZKb2gQz7c4hpX7BMKpEnrpGhI7APd7GHMdvGg",
12-
"refreshTokenExpirationInDays": 1,
12+
"refreshTokenExpirationInMinutes": 1440,
1313
"authTokenExpirationInMinutes": 60,
1414
"issuer": "Certify.Server.Hub.Api"
1515
}

src/Certify.Server/Certify.Server.HubService/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"AllowedHosts": "*",
1010
"JwtSettings": {
1111
"secret": "8FdYdFZKb2gQz7c4hpX7BMKpEnrpGhI7APd7GHMdvGg",
12-
"refreshTokenExpirationInDays": 1,
12+
"refreshTokenExpirationInMinutes": 1440,
1313
"authTokenExpirationInMinutes": 60,
1414
"issuer": "Certify.Server.Hub.Api"
1515
}

0 commit comments

Comments
 (0)