Craig Armstrong
Craig Armstrong

Reputation: 156

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

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.

Upvotes: 0

Views: 1002

Answers (2)

Martin
Martin

Reputation: 3286

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:

Upvotes: 1

Neistow
Neistow

Reputation: 1244

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

Upvotes: 2

Related Questions