Inclusion of the current user in each service level call from the controller

I am performing model validation in my controllers, but the second business validation should take place at the service / business level. Usually this is due to user rights: Does the current user have access to customer / order information that he is trying to get or publish?

My first (and working) approach is to pass either the entire User instance or its Id (by calling User.Identity.GetUserId() ), which would be sufficient in most cases - not all the time. So I will have something like this:

 public IHttpActionResult Get(int id) { try { var customer = customerService.GetById(id, userId); return Ok(customer); } catch (BusinessLogicException e) { return CreateErrorResponse(e); } } 

But I do not really like the fact that with this approach I will have to include an additional parameter in almost every call of my service level. If I call the GetById() method, I want to get something by providing an identifier, not an identifier and a user identifier.

A simple workaround would be something in this direction that also works:

 public IHttpActionResult Get(int id) { customerService.SetCurrentUser(User.Identity.GetUserId()); try { var customer = customerService.GetById(id); return Ok(customer); } catch (BusinessLogicException e) { return CreateErrorResponse(e); } } 

But instead of making a separate call to set the current user, I would like it to be done automatically every time the service is called. How can i do this?

This is what my service looks like:

 public class CustomerService : EntityService<Customer>, ICustomerService { public string UserId; IContext context; public CustomerService(IContext context) : base(context) { this.context = context; this.dbSet = context.Set<Customer>(); } public void SetCurrentUser(string userId) { UserId = userId; } public DTO.Customer GetById(int id) { if (!IsAccessibleByUser(id)) { throw new BusinessLogicException(ErrorCode.UserError, "UserId: " + UserId); } return dbSet.FirstOrDefault(x => x.Id == id).ToDto<Customer, DTO.Customer>(); } public bool IsAccessibleByUser(int id) { return context.UsersAPI.Any(a => a.AspNetUsersID == UserId); } } 
+6
source share
3 answers

I would rather perform this authorization logic in a custom authorization filter. There is no need to even get to the controller action code if the user has not been authenticated or authorized.

For example, you might have something like this:

 public class MyAuthorizeAttribute : AuthorizeAttribute { protected override bool AuthorizeCore(HttpContextBase httpContext) { var authorized = base.AuthorizeCore(httpContext); if (!authorized) { return false; } var rd = httpContext.Request.RequestContext.RouteData; // Get the id of the requested resource from the route data string resourceId = rd.Values["id"] as string; if (string.IsNullOrEmpty(resourceId)) { // No id of resource was specified => we do not allow access return false; } string userId = httpContext.User.Identity.GetUserId(); return IsAccessibleByUser(resourceId, userId); } private bool IsAccessibleByUser(string resourceId, string userId) { // You know what to do here => fetch the requested resource // from your data store and verify that the current user is // authorized to access this resource } } 

and then you can decorate your controllers or actions that require this permission with a custom attribute:

 [MyAuthorize] public IHttpActionResult Get(int id) { try { // At this stage you know that the user is authorized to // access the requested resource var customer = customerService.GetById(id); return Ok(customer); } catch (BusinessLogicException e) { return CreateErrorResponse(e); } } 

Of course, this custom attribute can be further enhanced by using a custom filter provider that allows you to enter data contexts into it so that you can make the appropriate calls. Then you can only have the token attribute that the filter provider will use to decide whether it should follow the authorization logic or not.

+1
source

Do you need your service to take user ID into account when executing its logic? If so, then it is reasonable that this be part of the entrance. Or is it just more authorization, when the service should reject the request without processing, depending on who the user is (but as soon as it is allowed, it does not matter who the user is)? You can insert additional data into the WCF request header and then check it for an incoming request. But a) it’s a pain, and b) it’s not at all obvious from the service interface that the client must provide this data.

For this reason, I would put the user id in the input. I would not use it. This is really an input property, not a processing of the contents of this input.

An interceptor is still a good idea if you do not want your service to have additional responsibility for authorizing requests. This way you put the attribute in your service or service method and keep the authorization in the interceptor. If the user cannot call the service or method, he rejects the call before he ever reaches the class of service.

0
source

Late answer, but I understand your concern, the best practice that you want to achieve in your code and make it as clean as possible, I had the same problem as yours, and just like you, I felt that this is wrong, there should be a better way to do this, and in my case I used all my layers:
System.Threading.Thread.CurrentPrincipal.Identity
and this answer to SO describes it: fooobar.com/questions/1002519 / ...

and while I always use this, it's worth looking at what others have done:
Access to HttpContext and User Identity from the Data Layer
How to get user id inside separate assembly
Getting the current registered user ID in a class library outside of an ASP.NET Webforms application using forms authentication
Get the identifier of the current ASP.NET user.

0
source

All Articles