Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override AuthorizeAttribute in ASP.Net Core and respond Json status

I'm moving from ASP.Net Framework to ASP.Net Core.

In ASP.Net Framework with Web API 2 project, I can customize AuthorizeAttribute like this :

public class ApiAuthorizeAttribute : AuthorizationFilterAttribute
{
    #region Methods

    /// <summary>
    ///     Override authorization event to do custom authorization.
    /// </summary>
    /// <param name="httpActionContext"></param>
    public override void OnAuthorization(HttpActionContext httpActionContext)
    {
        // Retrieve email and password.
        var accountEmail =
            httpActionContext.Request.Headers.Where(
                    x =>
                        !string.IsNullOrEmpty(x.Key) &&
                        x.Key.Equals("Email"))
                .Select(x => x.Value.FirstOrDefault())
                .FirstOrDefault();

        // Retrieve account password.
        var accountPassword =
            httpActionContext.Request.Headers.Where(
                    x =>
                        !string.IsNullOrEmpty(x.Key) &&
                        x.Key.Equals("Password"))
                .Select(x => x.Value.FirstOrDefault()).FirstOrDefault();

        // Account view model construction.
        var filterAccountViewModel = new FilterAccountViewModel();
        filterAccountViewModel.Email = accountEmail;
        filterAccountViewModel.Password = accountPassword;
        filterAccountViewModel.EmailComparision = TextComparision.Equal;
        filterAccountViewModel.PasswordComparision = TextComparision.Equal;

        // Find the account.
        var account = RepositoryAccount.FindAccount(filterAccountViewModel);

        // Account is not found.
        if (account == null)
        {
            // Treat the account as unthorized.
            httpActionContext.Response = httpActionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);

            return;
        }

        // Role is not defined which means the request is allowed.
        if (_roles == null)
            return;

        // Role is not allowed 
        if (!_roles.Any(x => x == account.Role))
        {
            // Treat the account as unthorized.
            httpActionContext.Response = httpActionContext.Request.CreateResponse(HttpStatusCode.Forbidden);

            return;
        }

        // Store the requester information in action argument.
        httpActionContext.ActionArguments["Account"] = account;
    }

    #endregion

    #region Properties

    /// <summary>
    ///     Repository which provides function to access account database.
    /// </summary>
    public IRepositoryAccount RepositoryAccount { get; set; }

    /// <summary>
    ///     Which role can be allowed to access server.
    /// </summary>
    private readonly byte[] _roles;

    #endregion

    #region Constructor

    /// <summary>
    ///     Initialize instance with default settings.
    /// </summary>
    public ApiAuthorizeAttribute()
    {
    }

    /// <summary>
    ///     Initialize instance with allowed role.
    /// </summary>
    /// <param name="roles"></param>
    public ApiAuthorizeAttribute(byte[] roles)
    {
        _roles = roles;
    }

    #endregion
}

In my customized AuthorizeAttribute, I can check whether account is valid or not and return HttpStatusCode with message to client.

I'm trying to do the samething in ASP.Net Core, but no OnAuthorization for me to override.

How can I achieve the same thing as in ASP.Net Framework ?

Thank you,

like image 670
Redplane Avatar asked Sep 22 '16 11:09

Redplane


2 Answers

You're approaching this incorrectly. It never was really encouraged to write custom attributes for this, or to extend existing. With ASP.NET Core roles are still apart of the system for backwards compatibility but they are now also discouraged.

There is a great 2 part series on some of the driving architecture changes and the way that this is and should be utilized found here. If you want to still rely on roles you can do so, but I would suggest using policies.

To wire a policy do the following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy(nameof(Policy.Account), 
                          policy => policy.Requirements.Add(new AccountRequirement()));
    });

    services.AddSingleton<IAuthorizationHandler, AccountHandler>();
}

I created a Policy enum for convenience.

public enum Policy { Account };

Decorate entry points as such:

[
    HttpPost,
    Authorize(Policy = nameof(Policy.Account))
]
public async Task<IActionResult> PostSomething([FromRoute] blah)
{
}

The AccountRequirement is just a placeholder, it needs to implement the IAuthorizationRequirement interface.

public class AccountRequirement: IAuthorizationRequirement { }

Now we simply need to create a handler and wire this up for DI.

public class AccountHandler : AuthorizationHandler<AccountRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        AccountRequirement requirement)
    {
        // Your logic here... or anything else you need to do.
        if (context.User.IsInRole("fooBar"))
        {
            // Call 'Succeed' to mark current requirement as passed
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Additional Resources

  • ASP.NET Core Security -- All the things
like image 159
David Pine Avatar answered Nov 10 '22 22:11

David Pine


My comment looks bad as a comment so I post an answer but only useful if you use MVC:

// don't forget this 
services.AddSingleton<IAuthorizationHandler, MyCustomAuthorizationHandler>();
services
   .AddMvc(config => { var policy = new AuthorizationPolicyBuilder() 
      .RequireAuthenticatedUser() .AddRequirements(new[] { new MyCustomRequirement() }) 
       .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }) 

I also noticed that async keyword is superfluous for "HandleRequirementAsync" signature, in question code. And I guess that returning Task.CompletedTask could be good.

like image 35
barbara.post Avatar answered Nov 10 '22 22:11

barbara.post