I'm thinking that perhaps my entire idea of how to approach this is wrong, so let me explain what I'm trying to do.
I have a UserId that is a property contained within my JWT token.
On many of my REST endpoints, I need to read that UserId to use it within my DB queries.
I implemented a filter which intercepts all of my calls and decodes my JWT and assigns the UserId value into a static Globals class that I had created.
I just realised now though, that that class is GLOBAL. As in, the values are actually shared across the entire server for anybodies REST requests. I intended for the value to essentially just be transiently available for the duration of each individual request.
How can I change my implementation so that I can globally access the UserId contained in the JWT token for the current request.
CodePudding user response:
My suggestion is to make some kind of abstraction e.g ICurrentUser
and make an implementation, which will take UserId
from HttpContext
.
// Define in Domain/Application project
public interface ICurrentUser
{
public string? Id { get; set; }
}
// Implement in ASP.NET project
public class CurrentUser : ICurrentUser
{
public CurrentUser(IHttpContextAccessor contextAccessor)
{
var user = contextAccessor.HttpContext?.User;
if (user == null)
{
return;
}
Id = user.FindFirstValue(ClaimTypes.NameIdentifier) ?? user.FindFirstValue(JwtClaimTypes.Subject);
}
public string? Id { get; set; }
}
Also, don't forget to add .AddHttpContextAccessor()
call for you services
CodePudding user response:
If you want something to be available for the duration of an individual request I would recommend using a service registered as scoped see Scoped Services
But lets start from the beginning. First implement a service itself like:
public UserService : IUserService
{
private readonly IHttpContextAccessor _accessor;
/// inject the `IHttpContextAccessor` to access the actual
/// request / token / headers etc.
public UserService(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public async Task<string> GetUserIdAsync()
{
var userId = await GetUserIdFromTokenAsync();
return userId;
}
private Task<string> GetUserIdFromTokenAsync()
{
/// Add your logic here to get or parse the
/// user id from the token or do some other stuff to get the user id.
/// ... or get the user id from the current User object claim
/// depends on your auth settings `_accessor.HttpContext?.User`
var token = _accessor... // from headers?
return userId;
}
}
/// Always use an interface to make it well testable and mockable for unit tests
public interface IUserService
{
Task<string> GetUserIdAsync();
}
Then in your dependency injection part (Startup.cs
or Program.cs
depends which tempate you have selected).
/// register the `IHttpContextAccessor` to be able to inject it.
services.AddHttpContextAccessor();
/// register your `UserService` as scoped!
services.AddScoped<IUserService, UserService>();
Now you can use this in all your services and controllers (which are at least also registered as scoped). This will resolve the service per request.
/// In a data service
class YourDataService
{
private readonly IUserService _userService;
/// Inject the `IUserService` wherever you need it now to
/// receive the current user Id.
public YourDataService(IUserService service)
{
_userService = service
}
public async Task DoYourQueryStuffAsync()
{
var userId = await _userService.GetUserIdAsync();
/// Your application logic with the provided userId
///
}
}
/// The same applies for a controller
[ApiController]
[Route("values")]
public class ValuesController : ControllerBase
{
private readonly IUserService _userService;
/// Inject the `IUserService` wherever you need it now to
/// receive the current user Id.
public ValuesController(IUserService service)
{
_userService = service
}
[Authorized]
[HttpGet]
public async Task<IActionResult> Query()
{
var userId = await _userService.GetUserIdAsync();
/// Your application logic with the provided userId
///
var queryresult = await ...
return Ok(queryresult);
}
}
Notes at the end: Do not fall into the trap to consume scoped services from a singleton service this is not working because singletons are persistent without the request context.
Documentation links: