Implement token caching and refresh mechanism for auth.
Added distributed caching for access and refresh tokens in both `AuthController` and `AppProxyController`. Introduced logic to handle token expiration and refresh, ensuring continuous authorization through token renewal and session management.
This commit is contained in:
parent
4ffc2a135c
commit
cf6ce0d736
@ -1,9 +1,8 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Server.HttpSys;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth;
|
||||
using Suspectus.Gandalf.Core.Abstractions.Extensions;
|
||||
using Suspectus.Gandalf.Palantir.Client;
|
||||
|
||||
@ -49,6 +48,11 @@ public class AppProxyController : ControllerBase
|
||||
}
|
||||
|
||||
var token = await GetToken(sessionCookie, appId, cancellationToken);
|
||||
|
||||
if (token is null)
|
||||
{
|
||||
return Unauthorized("Session expired.");
|
||||
}
|
||||
|
||||
_httpClient.BaseAddress = new Uri(appInfo.BaseAddress);
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
@ -78,32 +82,82 @@ public class AppProxyController : ControllerBase
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
private async Task<string> GetToken(string subjectId, string appId, CancellationToken cancellationToken)
|
||||
private async Task<string?> GetToken(string subjectId, string appId, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Get actual token for subject and app
|
||||
var accessTokenCacheKey = GetCacheKey(subjectId, appId, "access_token");
|
||||
var accessToken = await _tokenCache.GetStringAsync(accessTokenCacheKey, cancellationToken);
|
||||
|
||||
if (accessToken is not null)
|
||||
if (accessToken is not null)
|
||||
{
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
var refreshTokenCacheKey = GetCacheKey(subjectId, appId, "refresh_token");
|
||||
var refreshToken = await _tokenCache.GetStringAsync(refreshTokenCacheKey, cancellationToken);
|
||||
|
||||
if (refreshToken is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tokenRequestCommandResponse = await _client.Internal.Auth.Token(new TokenRequestCommand
|
||||
{
|
||||
GrandType = GrandType.RefreshToken,
|
||||
RefreshToken = refreshToken,
|
||||
ClientId = appId
|
||||
});
|
||||
|
||||
if (tokenRequestCommandResponse.IsFaulted)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tokenRequestResponse = tokenRequestCommandResponse.GetValue();
|
||||
|
||||
if (tokenRequestResponse is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const string newToken = "totallyARealToken"; // Replace with actual token retrieval logic
|
||||
await _tokenCache.SetStringAsync(
|
||||
accessTokenCacheKey,
|
||||
newToken,
|
||||
tokenRequestResponse.AccessToken,
|
||||
new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(1),
|
||||
AbsoluteExpiration = tokenRequestResponse.AccessTokenExpiresAt.AddSeconds(-10),
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
accessToken = newToken;
|
||||
|
||||
return accessToken;
|
||||
var refreshTokenCacheExpiration = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10);
|
||||
|
||||
await _tokenCache.SetStringAsync(
|
||||
refreshTokenCacheKey,
|
||||
tokenRequestResponse.RefreshToken,
|
||||
new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = refreshTokenCacheExpiration,
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
UpdateSessionCookie(subjectId, refreshTokenCacheExpiration);
|
||||
return tokenRequestResponse.AccessToken;
|
||||
}
|
||||
|
||||
private string GetCacheKey(string subjectId, string appId, string tokenType)
|
||||
{
|
||||
return $"{subjectId}:{appId}:{tokenType}";
|
||||
}
|
||||
|
||||
private void UpdateSessionCookie(string subjectId, DateTimeOffset refreshTokenCacheExpiration)
|
||||
{
|
||||
Response.Cookies.Append("MithrandirSession", subjectId, new CookieOptions
|
||||
{
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Expires = refreshTokenCacheExpiration
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands;
|
||||
using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth;
|
||||
using Suspectus.Gandalf.Core.Abstractions.Extensions;
|
||||
using Suspectus.Gandalf.Palantir.Client;
|
||||
using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth;
|
||||
|
||||
namespace Suspectus.Gandalf.Bridgekeeper.Api.Controllers;
|
||||
|
||||
@ -10,12 +12,14 @@ namespace Suspectus.Gandalf.Bridgekeeper.Api.Controllers;
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IPalantirClient _client;
|
||||
private readonly IDistributedCache _tokenCache;
|
||||
|
||||
public AuthController(IPalantirClient client)
|
||||
public AuthController(IPalantirClient client, IDistributedCache tokenCache)
|
||||
{
|
||||
_client = client;
|
||||
_tokenCache = tokenCache;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public async Task<IActionResult> Check()
|
||||
{
|
||||
@ -23,28 +27,72 @@ public class AuthController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginCommand loginCommand)
|
||||
public async Task<IActionResult> Login([FromBody] ValidateCredentialsCommand validateCredentialsCommand, [FromQuery] string? clientId, CancellationToken cancellationToken)
|
||||
{
|
||||
var validationResult = await _client.Internal.Auth.ValidateCredentials(new ValidateCredentialsCommand { Password = loginCommand.Password, UsernameOrEmail = loginCommand.UsernameOrEmail });
|
||||
if (clientId is null)
|
||||
{
|
||||
var masterAppInfoResponse = await _client.Internal.App.GetMasterInfo();
|
||||
|
||||
return validationResult.Match<IActionResult>(valid =>
|
||||
if (masterAppInfoResponse.IsFaulted)
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return BadRequest("Invalid username or password.");
|
||||
}
|
||||
return BadRequest($"Failed to retrieve master app info.");
|
||||
}
|
||||
|
||||
Response.Cookies.Append("MithrandirSession", loginCommand.UsernameOrEmail, new CookieOptions
|
||||
{
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Expires = DateTime.UtcNow.AddMinutes(30)
|
||||
});
|
||||
clientId = masterAppInfoResponse.GetValue().Id;
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}, e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")
|
||||
var tokenRequestCommandResponse = await _client.Internal.Auth.Token(new TokenRequestCommand
|
||||
{
|
||||
Password = validateCredentialsCommand.Password,
|
||||
Username = validateCredentialsCommand.UsernameOrEmail,
|
||||
GrandType = GrandType.Password,
|
||||
ClientId = clientId
|
||||
});
|
||||
|
||||
if (tokenRequestCommandResponse.IsFaulted)
|
||||
{
|
||||
return tokenRequestCommandResponse.Match<IActionResult>(
|
||||
BadRequest,
|
||||
e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")
|
||||
);
|
||||
}
|
||||
|
||||
var tokenRequestResponse = tokenRequestCommandResponse.GetValue();
|
||||
|
||||
if (tokenRequestResponse is null)
|
||||
{
|
||||
return BadRequest("Invalid username or password.");
|
||||
}
|
||||
|
||||
await _tokenCache.SetStringAsync(
|
||||
GetCacheKey(tokenRequestResponse.SubjectId, clientId, "access_token"),
|
||||
tokenRequestResponse.AccessToken,
|
||||
new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = tokenRequestResponse.AccessTokenExpiresAt.AddSeconds(-10),
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
await _tokenCache.SetStringAsync(
|
||||
GetCacheKey(tokenRequestResponse.SubjectId, clientId, "refresh_token"),
|
||||
tokenRequestResponse.RefreshToken,
|
||||
new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10),
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
Response.Cookies.Append("MithrandirSession", tokenRequestResponse.SubjectId, new CookieOptions
|
||||
{
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Expires = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10)
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
@ -60,4 +108,9 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
private string GetCacheKey(string subjectId, string appId, string tokenType)
|
||||
{
|
||||
return $"{subjectId}:{appId}:{tokenType}";
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user