1

I am trying to find a way to get a UserId string to my service layer without having to modify the request passed from the FE.

I am using Auth0 to authenticate users for an application. I use middleware to access the users id and set it to the context.items enumerable using this code:

app.Use(async (context, next) =>
{
    var userIdClaim = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
    if (userIdClaim is not null)
    {
        context.Items["UserId"] = userIdClaim.Value;
    }

    await next.Invoke();
});

And this works fine, so I get get the UserId in my controller using

(string)HttpContext.Items["UserId"];

Currently, I modified the request object received from the front end to include a nullable string UserId so I can pass it along with this code:

[HttpGet]
public async Task<AddressResponse.GetIndex> GetIndexAsync([FromQuery] AddressRequest.GetIndex request)
{
    request.UserId = (string)HttpContext.Items["UserId"];
    return await addressService.GetIndexAsync(request);
}

public class AddressRequest 
{
    public class GetIndex : AuthenticatedData {}
}

public class AuthenticatedData
{
    public string? UserId { get; set; }
}

Now for my detail and delete functions I only pass a primitive int to the route/body so I cannot modify it to include a UserId. Should I make every request an object with UserId even if it seems a bit overkill? Or is there another way to receive the UserId in the service layer?

I am using a shared interface between my Blazor app and the api, so I cannot just modify the interface to include a UserId (I think).

And is there a way to set the request UserId in the middleware itself and not with every controller function?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jasper B
  • 346
  • 3
  • 17
  • Why are you using middleware instead of accessing [`ControllerBase.User`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.user?view=aspnetcore-7.0) inside middleware? – Guru Stron Dec 06 '22 at 20:45
  • Mainly because I wanted to change the code in the middleware so the middleware does all the work – Jasper B Dec 06 '22 at 22:56

1 Answers1

1

First of all I would argue that in general case you should not add any properties on incoming model that you are not expecting to receive from the frontend otherwise your service can become a subject for kind of over posting attacks (for example for some reason in one service you forgot to set the UserId from the claims and malicious user provided one).

As for answering your question - one option is to try looking into custom model binding and implementing a special binder for the UserId property (see this answer).

Personally I would go with wrapping IHttpContextAccessor into some service like IUserIdProvider and use it in services. Something along this lines:

interface IUserIdProvider
{
    string? GetUserId();
}

class UserIdProvider : IUserIdProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserIdProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    public string? GetUserId() => _httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
}

builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IUserIdProvider, UserIdProvider>();

Though see the notes on IHttpContextAccessor usage. Alternatively you can create pair of interfaces IUserIdSetter and IUserIdProvider (implemented by the same scoped class) and set user id for provider in the middleware.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Alright! I will be testing the method with the `IHttpContextAccessor` since I can't really use modelbinding as my DTO's are in a shared project – Jasper B Dec 06 '22 at 23:10
  • 1
    You are a true live saver (and school project savior) haha! Thanks for the quick response and help – Jasper B Dec 06 '22 at 23:32