1

I am creating an authentication and authorization handler for internal authorization purposes. My intention is to make it easy for my colleagues to implement the solution into their own projects. We are using Azure AD for authentication, and for authorization we are using Azure Groups. In order to do that, I feel like I am stuck on figuring out how to add authorization policies in an efficient way.

Right now I'm adding it through the officially described way in the Program class of my Client project in a Blazor webassembly hosted configuration:

            builder.Services.AddAuthorizationCore(options =>
                options.AddPolicy("PolicyName", policy =>
                {
                    policy.RequireClaim("ClaimType", "ClaimValue");
                })
            );

This works fine, but it's not intuitive, as any given project could require several different policies

I have also added a custom Authorization Policy Provider, as described in this documentation from Microsoft:

https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-6.0

I figured this would be what I was looking for, based on their description for this documentation, especially the first couple of lines in the documentation. But I still can't seem to get it to work as intended, without specifically adding each policy manually.

If need be I can show my custom implementation of the Authorization Policy Provider, but it is pretty much exactly as seen in the Github for the documentation.

Roddik
  • 45
  • 1
  • 6

1 Answers1

5

Policies are most commonly registered at application startup in the Startup classes ConfigureServices method.

public void ConfigureServices(IServiceCollection services)    
{
    services.AddAuthorization(config =>
    {
        config.AddPolicy("IsDeveloper", policy => policy.RequireClaim("IsDeveloper","true"));
       });
}

the policy IsDeveloper requires that a user have the claim IsDeveloper with a value of true.

Roles you can apply policies via the Authorize attribute.

[Route("api/[controller]")]
[ApiController]
public class SystemController 
{
    [Authorize(Policy = “IsDeveloper”)]
    public IActionResult LoadDebugInfo()
    {
        // ...
    }
}

Blazors directives and components also work with policies.

@page "/debug"
@attribute [Authorize(Policy = "IsDeveloper")]
< AuthorizeView Policy="IsDeveloper">
    < p>You can only see this if you satisfy the IsDeveloper policy.< /p>
< /AuthorizeView>

Easier Management

With role-based auth, if we had a couple of roles which were allowed access to protected resources - let’s say admin and moderator. We would need to go to every area they were permitted access and add an Authorize attribute.

[Authorize(Roles = "admin,moderator")]

This doesn’t seem too bad initially, but what if a new requirement comes in and a third role, superuser, needs the same access? We now need to go round every area and update all of the roles. With policy-based auth we can avoid this.

We can define a policy in a single place and then apply it once to all the resources which require it. Then when extra roles need to be added, we can just update the policy from the central point without the need to update the individual resources.

 public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(config =>
    {
    config.AddPolicy("IsAdmin", policy => policy.RequireRole("admin", "moderator", "superuser"));
    });
}
[Authorize(Policy = "IsAdmin")]

Creating shared policies

We need to install the Microsoft.AspNetCore.Authorization package from NuGet in order to do this.

After that create a new class called Policies with the following code.

public static class Policies
{
public const string IsAdmin = "IsAdmin";
public const string IsUser = "IsUser";
public static AuthorizationPolicy IsAdminPolicy()
{
    return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
                                           .RequireRole("Admin")
                                           .Build();
}

public static AuthorizationPolicy IsUserPolicy()
{
    return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
                                           .RequireRole("User")
                                           .Build();
}
}

Here we’re using the AuthorizationPolicyBuilder to define each policy, both require the user to be authenticated then be in either the Admin role or User role, depending on the policy.

Configuring the server

Rregistering the policies in ConfigureServices in the Startup class. Add the following code under the existing call to AddAuthentication.

services.AddAuthorization(config =>
{
    config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
    config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});

registering each policy and using the constants we defined in the Policies class to declare their names, which saves using magic strings.

If we move over to the SampleDataController we can update the Authorize attribute to use the new IsAdmin policy instead of the old role.

[Authorize(Policy = Policies.IsAdmin)]
[Route("api/[controller]")]
public class SampleDataController : Controller

Again, we can use our name constant to avoid the magic strings.

Configuring the client

Our server is now using the new policies we defined, all that’s left to do is to swap over our Blazor client to use them as well.

As with the server we’ll start by registering the policies in ConfigureServices in the Startup class. We already have a call to AddAuthorizationCore so we just need to update it.

services.AddAuthorizationCore(config =>
{
    config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
    config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});

In Index.razor, update the AuthorizeView component to use policies - still avoiding the magic strings.

< AuthorizeView Policy="@Policies.IsUser">
    < p>You can only see this if you satisfy the IsUser policy.< /p>
< /AuthorizeView>
< AuthorizeView Policy="@Policies.IsAdmin">
    < p>You can only see this if you satisfy the IsAdmin policy.< /p>
< /AuthorizeView>

Finally, update FetchData.razors Authorize attribute.

@attribute [Authorize(Policy = Policies.IsAdmin)]

Refer here

Delliganesh Sevanesan
  • 4,146
  • 1
  • 5
  • 15