Search code examples
c#azure-active-directoryauthorize-attributeapi-authorization

Custom Authorize attribute role is not working web api authentication


I'm facing an issue while working with web api azure ad authentication

I'm having controller like below, the one which having giving proper response, But the one which having customauthorization roles throwing error as "Authentication has been for this request".

[RoutePrefix("api/hospitals")]
public class hospitals : ApiController
{
    [Route("GetAll")]
    [HttpGet]
    [Authorize]
    public async Task<IEnumerable<Hospitals>> GetAll()
    {
        // return ok;
    }
    [Route("Getbeds")]
    [HttpGet]
    [SmAuthorize(Constants.Roles.Admin,
        Constants.Roles.HotSpitalAdmin,
        Constants.Roles.QA)]
    public async Task<IEnumerable<Hospitals>> Getbeds()
    {
        // return ok;
    }
}

The Getbeds method is throwing an error as "Authorization has been request".

Please find me Custom attribute class as well

public class SmAuthorizeAttribute : AuthorizeAttribute
{
    public SmAuthorizeAttribute(params string[] roles)
    {
        this.Roles = string.Join(",", roles.Select(s => s.Trim()).ToArray());
    }
}

Can anyone help on this ?


Solution

  • You can refer to this SO question's answer by Derek Greer for Dot Net core, additionally I will reiterate the answer below -

    The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).

    The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.

    While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.

    The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:

    public class ClaimRequirementAttribute : TypeFilterAttribute
    {
        public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
        {
            Arguments = new object[] {new Claim(claimType, claimValue) };
        }
    }
    
    public class ClaimRequirementFilter : IAuthorizationFilter
    {
        readonly Claim _claim;
    
        public ClaimRequirementFilter(Claim claim)
        {
            _claim = claim;
        }
    
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
            if (!hasClaim)
            {
                context.Result = new ForbidResult();
            }
        }
    }
    
    
    [Route("api/resource")]
    public class MyController : Controller
    {
        [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
        [HttpGet]
        public IActionResult GetResource()
        {
            return Ok();
        }
    }
    

    Part of this answer for .NET Framework-

    Recommended Custom Attribute class:

    public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
    {
        private readonly PermissionAction[] permissionActions;
    
        public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
        {
            this.permissionActions = permissionActions;
        }
    
        protected override Boolean IsAuthorized(HttpActionContext actionContext)
        {
            var currentIdentity = actionContext.RequestContext.Principal.Identity;
            if (!currentIdentity.IsAuthenticated)
                return false;
    
            var userName = currentIdentity.Name;
            using (var context = new DataContext())
            {
                var userStore = new UserStore<AppUser>(context);
                var userManager = new UserManager<AppUser>(userStore);
                var user = userManager.FindByName(userName);
    
                if (user == null)
                    return false;
    
                foreach (var role in permissionActions)
                    if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
                        return false;
    
                return true;
            }
        }
    }