I'm not sure whether I coined the term right above in title but below is my problem summary.
I have developed REST API's using ASP.NET WebAPI with Identity for Authentication.
For E.g. say I have a Company API lying at http://<myurl>/api/Companies
for GET and POST and http://<myurl>/api/Companies/{id}
for GETBYID, PUT and DELETE.
While testing the API's I found that once a user is Authenticated (by Token), s/he can edit any company.
I'd like to restrict users to edit / delete companies (and other entities too) on which s/he belongs to (according to the logical structure of table).
Solution On My Mind :
The first idea comes to my mind is using ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
to find the user who's requesting the operation and then find the Company s/he belongs to say by user.CompanyID
and match with the Payload if s/he is trying to edit / delete or perform some other operation on same entity he belongs to.
Yes, for different entities it'll be user.EntityID
. But is this the right approach to do this?
Because this way, I have to write this kind of code on all my controller Edit and Delete operation.
I know, ASP.NET cannot restrict things on logical structure. Such as here in my case, it cannot determine, whether user is trying to edit the company s/he belongs to or not, as that's something logical to DB Relation. But is there anything kind of [Authorize]
attribute, that I can develop which will contain the above logic instead of cluttering the controller?
UPDATE : Not sure if everyone is understanding the situation, but here's some more story to clarify. It's a SaaS App in Cloud with Multi-Tenant Database. So each and every Company have their own set of Users and other Entities who should not be able to access Entities outside their Company (as per table relationship)
The API's are being consumed by Front-End WebApp, so Restricting by Views are what I'm doing for now. But I'm concerned about what if someone just inspects the JS File and calls the API's directly?
I need something to limit the user within it's entities only.
There are many ways to slice this, but I think the most secure would be to setup SQL Views
that allow your users to access data from the underlying tables, filtering the data based on the user / organization / roles / claims. If you're not familiar with views, you basically create your underlying tables structure, then views are created as a layer above the tables, each of them is generated via a query, then your top level app can simply query the views.
You may create a custom AuthorizationFilterAttribute
class to apply additional filters declaratively (just like how [Authorize]
works). The following would give you an [AuthorizeActive]
filter that you could apply to actions which would check the users ID against the database to ensure they are active
on every request:
using System;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Microsoft.AspNet.Identity;
public class AuthorizeActiveAttribute : AuthorizationFilterAttribute
{
private readonly IUserService _userService;
public AuthorizeActiveAttribute() {
_userService = Injection.Get<IUserService>();
}
public override void OnAuthorization(HttpActionContext filterContext) {
if (filterContext == null)
throw new ArgumentNullException(nameof(filterContext));
if (SkipAuthorization(filterContext)) return;
var userId = ClaimsPrincipal.Current?.Identity?.GetUserId<int>();
if (userId != null && userId > 0 && _userService.GetUserStatus((int) userId)) {
base.OnAuthorization(filterContext);
}
else {
filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
private static bool SkipAuthorization(HttpActionContext filterContext) {
var hasAnonymousAction = filterContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0;
var hasAnonymousController = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0;
return hasAnonymousAction || hasAnonymousController;
}
}
In addition you can setup a base api controller containing common logic for authorization (perhaps you want all APIs to be authorized by default):
/// <summary>
/// Base controller for resource related API calls.
/// </summary>
[AuthorizeActive]
public abstract class ResourceController : ApiController {
public bool IsAuthorized => User?.Identity?.IsAuthenticated == true;
public int ActualUserId => GetIntegerClaimValue("actualuserid");
public int UserId => GetIntegerClaimValue("userid");
public int UserTypeId => GetIntegerClaimValue("usertypeid");
public UserTypes UserType => (UserTypes)UserTypeId;
public int ActualOrganizationId => GetIntegerClaimValue("actualorganizationid");
public int OrganizationId => GetIntegerClaimValue("organizationid");
public string Fingerprint => GetClaimValue("fingerprint");
protected string GetClaimValue(string shortName) => (User?.Identity as ClaimsIdentity)?.Claims?.GetValue($"http://schemas.example.com/identity/claims/{shortName}");
protected int GetIntegerClaimValue(string shortName) => Convert.ToInt32(GetClaimValue(shortName));
}
public HomeController : ResourceController {
}
This example is pulling custom claims out of the .NET user identity that correspond to the users organization, whether they are impersonating, etc.
Now, all of your API calls are authorized and secured to only allow users who are active in your database by default, to allow access anonymously, decorate the method with [AllowAnonymous]
. Within your controllers that inherit ResourceController
you will always have access to any of its protected properties, making it simple to grab the users organization ID and use it in your queries.