10

Blazor server-side, .NET Core 3.1.x Look over the examples on authorization, I am trying to get a solution for a custom authorization filter/attribute. I simply need to check the user identity during Authorization.

https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1

At the top of a Blazor page, after @page

@attribute [MyAuthFilter]

The filter. OnAuthorization never gets hit however.

public class MyAuthFilter: AuthorizeAttribute,IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var httpContext = context.HttpContext;


        // get user name
        string userName = httpContext.User.Identity.Name;

        // todo - call method to check user access
        // check against list to see if access permitted
        //context.Result = new UnauthorizedResult();

    }
}
gbianchi
  • 2,129
  • 27
  • 35
bitshift
  • 6,026
  • 11
  • 44
  • 108
  • Did you consider using Policy based authorization instead, with something like : `[Authorize(Policy = "a-policy")] : https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3.1 – agua from mars May 22 '20 at 18:53
  • Yes, and that might be the wya I need to go. Just need to check the user identity against an existing Db and if not found, redirect to a page – bitshift May 22 '20 at 19:05
  • So you have the answer then – agua from mars May 22 '20 at 19:12

2 Answers2

9

The following code snippet describes how you can perform the authorization process, and how and where to display content for authorized users. You can built your own component based on the code shown here:

Profile.razor

@page "/profile"
@page "/profile/{id}"


<AuthorizeView Policy="CanEditProfile" Resource="@ID">
    <NotAuthorized>
       <h2 class="mt-5">You are not authorized to view this page</h2>
    </NotAuthorized>
<Authorized>
    <div class="container my-profile">
        <h2>My Profile</h2>
        --- Place here all the content you want your user to view ----
    </div>
 </Authorized>
</AuthorizeView>

@code {

    [Parameter]
    public string ID { get; set; }
}  

ProfileHandler.cs

public class ProfileHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        if (context.User != null)
        {
            var pendingRequirements = context.PendingRequirements.ToList();

            foreach (var requirement in pendingRequirements)
            {
                if (requirement is ProfileOwnerRequirement)
                {
                    // get profile id from resource, passed in from blazor 
                    //  page component
                    var resource = context.Resource?.ToString();
                    var hasParsed = int.TryParse(resource, out int 
                                                       profileID);
                    if (hasParsed)
                    {

                        if (IsOwner(context.User, profileID))
                        {
                            context.Succeed(requirement);
                        }
                    }
                }
            }

        }
        return Task.CompletedTask;
    }
    private bool IsOwner(ClaimsPrincipal user, int profileID)
    {
        // compare the requested memberId to the user's actual claim of 
        // memberId
        //  var isAuthorized = context.User.GetMemberIdClaim();
        // now we know if the user is authorized or not, and can act 
        // accordingly

        var _profileID = user.GetMemberIDClaim();


        return _profileID == profileID;
     }

 }

ProfileOwnerRequirement.cs

 public class ProfileOwnerRequirement : IAuthorizationRequirement
 {
    public ProfileOwnerRequirement() { }

 }

Startup class

services.AddSingleton<IAuthorizationHandler, ProfileHandler>();

        services.AddAuthorization(config =>
        {
            config.AddPolicy("CanEditProfile", policy =>
                policy.Requirements.Add(new ProfileOwnerRequirement()));
        });

Hope this helps!

enet
  • 41,195
  • 5
  • 76
  • 113
  • 1
    Yes, thanks! I have gone that direction now. See my other post here, for some reason my Handler code isnt geting called. https://stackoverflow.com/questions/61963205/handlerequirementasync-not-being-called-in-auth-handler-in-blazor-app I really dont need segregated authorized/unauthorized content but rather just want to do some custom logic on authorization, so the difference in what you show is that im using a page-level authorize attribute @attribute [Authorize(Policy = Policies.IsValidUser)] – bitshift May 22 '20 at 20:42
0

If you just want to validade one kind of requirement you can simplify @enet response replaceing ProfileHandler.cs with this:

public class ProfileHandler : AuthorizationHandler<ProfileOwnerRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProfileOwnerRequirement requirement)
    {
        if (context.User.Identity.IsAuthenticated)
        {               
            var resource = context.Resource?.ToString();
            
            var hasParsed = int.TryParse(resource, out int 
                                                   profileID);
            if (hasParsed)
            {
                 if (IsOwner(context.User, profileID))
                 {
                        context.Succeed(requirement);
                 }
            }                                
        }

        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, int profileID)
    {        
        var _profileID = user.GetMemberIDClaim();

        return _profileID == profileID;
     }
}

With HandleRequirementAsync, it only gets called once while if you use HandleAsync, it gets called multiple times, of course for simple validations makes no difference but if you have complex validations they will be run multiple times.

MarchalPT
  • 1,268
  • 9
  • 28