Authentication

First, even though this article is mainly about authorization. Authentication is the first step to pass the gate. 

Ourcraving REST API is using OAuth for authentication. It is very straightforward to adopt the Owin solution to apply OAuth authentication. If you want to use Microsoft Identity Framework, here is a highly recommended approach. We actually used the same approach with our own modification. If you do not want to use Identity Framework, here is another approach, which fundamentally is the same with the only difference in how to manage account, internally, or externally. 

Authentication is just the door man. After it, we still need to determine which room this user is allowed to enter. That is called Authorization. 

Authorization 

Most people understand role-based authorization, by using  

        [Authorize(Roles="Admin")]
        public void MyTestServiceMethod()

Role-based is very simple. In some cases, it is probably enough. However, life is never this simple. The first case that role-based doesn't work is that it cannot be used to authorize the resource owner, i.e. only the owner of the data can do, such as my profile, my restaurants, or my added dishes. Therefore, we need something not role-based.  

Since we have already used Owin middleware in Authentication, we decided to use the same strategy in Authorization. 

First, we need to override the existing Authorize attribute

        public class ResourceAuthorizeAttribute : AuthorizeAttribute

Next, we want to override the method to determine if the current is authorized:

        protected override bool IsAuthorized(HttpActionContext actionContext)
        {
            // first, we want to collect all the actions and resources we want to evaluate the access
            var actions = new List<Claim>();
            var action = ActionFromAttribute();
            if (action != null) actions.Add(action);
            actions.Add(actionContext.ActionFromController());

            var resources = new List<Claim>();
            var resourceList = ResourcesFromAttribute();
            if (resourceList != null) resources.AddRange(resourceList);
            resources.AddRange(actionContext.ResourceFromController());

            // filter "controller" since we're already adding it explicitly in the above code
            var routeClaims = actionContext.ResourcesFromRouteParameters().Where(x => x.Type != "controller");
            resources.AddRange(routeClaims);

            return base.IsAuthorized(actionContext)  // this continues to use the original <Authorize> attribute
                // next we check if the user has the right to access the action
                && CheckAccess(actionContext.Request, actions.ToArray(), resources.Distinct(new ClaimComparer()).ToArray()); 
        }

It will first try to do the original AuthorizeAttribute does and add an additional logic to check the resource ownership by passing the controller's name and the action's name. 

The CheckAccess method will locate our new Authorize manager from Owin 

protected virtual bool CheckAccess(HttpRequestMessage request, Claim[] actions, params Claim[] resources)
        {
            // calls an extension method to check if the current user is authorized to access the resource
            var task = request.CheckAccessAsync(actions, resources);

            if (task.Wait(60000))
            {
                return task.Result;
            }
            
            throw new TimeoutException();
        }

Here is our extension object 

public static class HttpRequestExtensions
    {
        public static Task<bool> CheckAccessAsync(this HttpRequestMessage request, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
        {
            var authorizationContext = new ResourceAuthorizationContext(
                request.GetOwinContext().Authentication.User ?? Principal.Anonymous,
                actions,
                resources, 
                request.GetRouteData());

            return request.CheckAccessAsync(authorizationContext);
        }

        public static Task<bool> CheckAccessAsync(this HttpRequestMessage request, ResourceAuthorizationContext authorizationContext)
        {
            return request.GetOwinContext().CheckAccessAsync(authorizationContext);
        }

        private static async Task<bool> CheckAccessAsync(this IOwinContext context, ResourceAuthorizationContext authorizationContext)
        {
            return await context.GetAuthorizationManager().CheckAccessAsync(authorizationContext);
        }

        private static IResourceAuthorizationManager GetAuthorizationManager(this IOwinContext context)
        {
            var am = context.Get<IResourceAuthorizationManager>(ResourceAuthorizationManagerMiddleware.Key);

            if (am == null)
            {
                throw new InvalidOperationException("No AuthorizationManager set.");
            }

            return am;
        }
    }

We create our own ResourceAuthorizationContext, which looks like 

public class ResourceAuthorizationContext
    {
        public IEnumerable<Claim> Action { get; set; }
        public IEnumerable<Claim> Resource { get; set; }
        public ClaimsPrincipal Principal { get; set; }
        public IHttpRouteData RequestParameters { get; set; }
        public string RequestingId { get; set; }

        public ResourceAuthorizationContext(ClaimsPrincipal principal, IEnumerable<Claim> action,
            IEnumerable<Claim> resource, IHttpRouteData requestParameters)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            if (resource == null)
            {
                throw new ArgumentNullException("resource");
            }

            if (principal == null)
            {
                throw new ArgumentNullException("principal");
            }

            Action = action;
            Resource = resource;
            Principal = principal;

            RequestParameters = requestParameters;
            RequestingId = InitializeRequestingId();
        }

        private string InitializeRequestingId()
        {
            // here we have a naming conversion: there must have a parameter named "id" in the endpoint
            if (RequestParameters != null)
            {
                foreach (var data in RequestParameters.Values)
                {
                    if (data.Key.Equals("id", StringComparison.InvariantCultureIgnoreCase))
                    {
                        return data.Value == null ? string.Empty : data.Value.ToString();
                    }
                }
            }

            return string.Empty;
        }

        public string GetValue(string parameterName)
        {
            if (RequestParameters != null)
            {
                foreach (var data in RequestParameters.Values)
                {
                    if (data.Key.Equals(parameterName, StringComparison.InvariantCultureIgnoreCase))
                    {
                        return data.Value == null ? string.Empty : data.Value.ToString();
                    }
                }
            }

            return string.Empty;
        }
    }

Our abstract ResourceAuthorizationManager is 

public abstract class ResourceAuthorizationManager : IResourceAuthorizationManager
    {
        public virtual Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
        {
            throw new NotImplementedException();
        }

        protected virtual Task<bool> Ok()
        {
            return Task.FromResult(true);
        }

        protected virtual Task<bool> Nok()
        {
            return Task.FromResult(false);
        }

        protected virtual Task<bool> Eval(bool val)
        {
            return Task.FromResult(val);
        }
    }

    public interface IResourceAuthorizationManager
    {
        Task CheckAccessAsync(ResourceAuthorizationContext context);
    }

 Now it is something we can register to Owin 

public class ResourceAuthorizationManagerMiddleware: OwinMiddleware
    {
        public const string Key = "idm:resourceAuthorizationManager";

        private readonly ResourceAuthorizationMiddlewareOptions _options;

        public ResourceAuthorizationManagerMiddleware(OwinMiddleware next, ResourceAuthorizationMiddlewareOptions options)
            : base(next)
        {
            _options = options;
        }

        public override async Task Invoke(IOwinContext context)
        {
            context.Environment[Key] = _options.Manager;
            await Next.Invoke(context);
        }
    }

 public class ResourceAuthorizationMiddlewareOptions
    {
        public ResourceAuthorizationMiddlewareOptions()
        {
            ManagerProvider = (env) => null;
        }
        public IResourceAuthorizationManager Manager { get; set; }
        public Func<IDictionary<string, object>, IResourceAuthorizationManager> ManagerProvider { get; set; }
    }

Finally, in the Startup, we can register our new ResourceAuthorization middleware: 

app.UseResourceAuthorization(new OurCravingAuthorizationManager(UnityConfig.GetConfiguredContainer()));

Now, when we want to authorize a resource call that is allowed only by the owner or the administrator, we can decorate the controller method like this: 

[ResourceAuthorize]
        public async Task<IHttpActionResult> UpdateProfile(long id, [FromBody] UpdateDinerBaseReq profile)

However, this is not done yet. We need to implement the actual resource manager 

public class OurCravingAuthorizationManager : ResourceAuthorizationManager
    {
        private readonly IUnityContainer _container;

        public OurCravingAuthorizationManager(IUnityContainer container)
        {
            _container = container;
        }

        public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
        {
            // we load the current requesting resource's name 
            string resourceKey = context.Resource.First().Value;
            if (!string.IsNullOrEmpty(resourceKey))
            {
                IResourceAuthorizationRule rule = null;
                try
                {
                    // trying to resolve it from the container
                    rule = _container.Resolve<IResourceAuthorizationRule>(resourceKey);
                }
                catch (Exception) 
                {
                    // here can only log, throwing anything won't return to the client side (at least I haven't found a way) 
                    // the only possible exception is the resource authorization rule is not registered 
                }

                // if we can find a rule (to authorize), we will run it
                if (rule != null)
                {
                    var action = context.Action.First().Value;
                    return Eval(rule.Validate(context, action));
                }
            }

            // we accept the action by default, but we might need to rethink about this 
            return Ok();
        }
    }

It is trying to locate an Authorization rule from the container. If such a rule exists, we will delegate the authorization logic to the rule object, otherwise we assume the user is authorized to call this method. 

In OurCraving, we have a few rules against a few controllers. The registration looks like this: 

container.RegisterType<IResourceAuthorizationRule, ProfileAuthorizationRule>(AuthorizationResources.Diner);

The implementation of the rule object is similar to:

public class ProfileAuthorizationRule : IResourceAuthorizationRule
    {
        private readonly IRepositoryAsync<Diner> _dinerRepository;
        private readonly IWebUserSession _userSession;

        public ProfileAuthorizationRule(IRepositoryAsync<Diner> dinerRepository, IWebUserSession userSession)
        {
            _dinerRepository = dinerRepository;
            _userSession = userSession;
        }

        public bool Validate(ResourceAuthorizationContext context, string action)
        {
            if (context.IsAdmin()) return true;

            switch (action)
            {
                case AuthorizationActions.Profile.GetAll:
                    return CanGetAll(context);
                case AuthorizationActions.Profile.UpdateProfile:
                case AuthorizationActions.Profile.UpdateDislikes:
                    return CanExecuteUpdateProfile(context);
                case AuthorizationActions.Profile.GetMyBasicInfo:
                    return CanGetMyBasicInfo();
                default:
                    return true;
            }
        }

In a nutshell, we want to check the action, and then determine if the current is authorized to call. 

This approach is courteous to Thinktecture IdentityModel, which is an excellent library for authentication and authorization. We used their approach as a starting point and added some our own modification, because we don't really need to integrate with their entire library. For complete details of our implementation, please visit our source code and take a look.