David Peden
David Peden

Reputation: 18424

How to separate authorization logic from controller action?

Given the following code:

public class BackupsController : ApiController
{
    private readonly IApiContext context;
    private readonly IBackupService backupService;

    public BackupsController(IApiContext context, IBackupService backupService)
    {
        this.context = context;
        this.backupService = backupService;
    }

    public HttpResponseMessage Get(Guid id)
    {
        if (id == Guid.Empty)
        {
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }

        IBackupView backup = backupService.Get(id);

        if (backup == null)
        {
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("BackupId '{0}' not found.", id));
        }

        if (!IsAuthorizedForBackup(backup))
        {
            throw new HttpResponseException(HttpStatusCode.Forbidden);
        }

        return Request.CreateResponse(HttpStatusCode.OK, backup);
    }

    private bool IsAuthorizedForBackup(IBackupView backup)
    {
        if (context.Principal.IsInRole(MembershipRole.Admin))
        {
            return true;
        }

        if (context.Principal.AllowDataSharing && backup.UserId == context.Principal.UserId)
        {
            return true;
        }

        if (backup.UserId == context.Principal.UserId && backup.Device.Uuid == context.DeviceUuid)
        {
            return true;
        }

        return false;
    }
}

Does it make sense to extract almost all of the method body into an authorization filter? I don't see a way to do that without retrieving the backup twice.

How would you go about separating the authorization concerns from the controller action?

Upvotes: 2

Views: 270

Answers (2)

What you are asking for is technically possible. Say, you implement an action filter and have some logic in the overridden OnActionExecuted to set the status code to Forbidden. I have not done it this way and it is only a suggestion for you to explore the feasibility. OnActionExecuted runs after the action method and it can access backup.

Another better alternative is to use claims based identity and implement a sub-class of ClaimsAuthorizationManager. CheckAccess(AuthorizationContext) takes in both action claims and resource claims. Attributes related to back up can be passed along as resource claims.

Upvotes: 0

Mehmet Ataş
Mehmet Ataş

Reputation: 11549

In order to separate security logic from controller logic I prefer to use Http Headers to carry security tokens between browser and controller and check that header value in a custom AuthorizeAttribute

For example;

In beforeSend function of JQuery's ajax function set the security token (which is previously taken from the server, see below)

beforeSend: function (xhr) {
    xhr.setRequestHeader('requestToken', model.requestToken);
}

Check the token in a custom AuthorizeAttribute

public class AuthAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        var token = HttpContext.Current.Request.Headers["requestToken"];
        // Do the authorization based on token
    }
}

Decorate the controller, whose actions require authorization, with the custom [Auth] Attribute, like:

[Auth]
public class SomeController : ApiController

We can send new token back to client again using Http Headers

HttpContext.Current.Response.Headers["requestToken"] = Guid.NewGuid();

And at client-side you can store it in success function of JQuery's ajax function for sending back in request

success: function (res, status, xhr) {
    model.requestToken = xhr.getResponseHeader('requestToken');
}

This may not handle your situation perfectly but main idea is carrying the (preferably encrypted) security data in Http Headers and dealing the security things in a custom AuthorizeAttribute

Upvotes: 3

Related Questions