Home > Mobile >  How can I create a variable that is globally available but transient per REST request
How can I create a variable that is globally available but transient per REST request

Time:11-10

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:

  • Related