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?
CodePudding user response:
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.