Setting global authorization policies using the DefaultPolicy and the FallbackPolicy in ASP.NET Core 3.x
Tags: aspnetcore, aspnetcoreidentity, auth
Published: 2020-06-28 18:02:26

---
title: Setting global authorization policies using the DefaultPolicy and the FallbackPolicy in ASP.NET Core 3.x
published: true
date: 2020-06-09 09:00:00 UTC
tags: ASPNETCore,ASPNETCoreIdentity,Auth
canonical_url: https://andrewlock.net/setting-global-authorization-policies-using-the-defaultpolicy-and-the-fallbackpolicy-in-aspnet-core-3/
---

 ![Setting global authorization policies using the DefaultPolicy and the FallbackPolicy in ASP.NET Core 3.x](https://andrewlock.net/content/images/2020/global_authorization.jpg)

ASP.NET Core has an extensive authorization system that you can use to create complex authorization policies. In this post, I look at the various ways you can apply these policies to large sections of your application.

> [I wrote about creating custom authorization policies several years ago](https://andrewlock.net/custom-authorisation-policies-and-requirements-in-asp-net-core/). For a more up-to-date look, [my new book ASP.NET Core in Action is currently in pre-release](https://www.manning.com/books/asp-net-core-in-action-second-edition?a_aid=aspnetcore-in-action&a_bid=44c089ee). Use code **mllock2** to get 50% off until June 10th 2020!

We'll start by configuring a global `AuthorizeFilter` and see why that's no longer the recommended approach in ASP.NET Core 3.0+. We'll then look at the alternative, using endpoint routing, as well as using Razor Page conventions to apply different authorization policies to different parts of your app. We'll also compare the _DefaultPolicy_ to the _FallbackPolicy_ see when each of them are applied, and how you can update them.

For the purposes of this post, I'll assume you have a standard Razor Pages application, with a _Startup.cs_ something like the following. The details of this aren't very important, I just assume you have already configured authentication and a UI system for your application.

```csharp
public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // Configure ASP.NET Core Identity + EF Core
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
        );

        services.AddDefaultIdentity<IdentityUser>()
            .AddEntityFrameworkStores<AppDbContext>();

        // Add Razor Pages services
        services.AddRazorPages();

        // Add base authorization services
        services.AddAuthorization();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseStaticFiles();

        // Ensure the following middleware are in the order shown
        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            // Add Razor Pages to the application
            endpoints.MapRazorPages();
        });
    }
}
```

At this point, you have authentication, and you want to start protecting your application. You could apply `[Authorize]` attributes to every Razor Page, but you want to be a bit safer, and apply authorization globally, to all the pages in your application. For the rest of this post, we look at the various options available.

## Globally applying an AuthorizeFilter

The first option is to apply an `AuthorizeFilter` globally to all your MVC actions and Razor Pages. This is the approach traditionally used in earlier versions of ASP.NET Core.

> Note that [this is not the recommend approach to apply authorization globally in ASP.NET Core 3.0+](https://docs.microsoft.com/aspnet/core/migration/22-to-30#authorization). You'll see other approaches later in the post.

For example, you can add an `AuthorizeFilter` to all your Razor Page actions when configuring your Razor Pages in `ConfigureServices` (you can configure MVC controllers in a similar way):

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before

    // Add a default AuthorizeFilter to all endpoints
    services.AddRazorPages()
        .AddMvcOptions(options => options.Filters.Add(new AuthorizeFilter()));
}
```

This is equivalent to decorating all your Razor Pages with an `[Authorize]` attribute, so users are authorized using the `DefaultPolicy` (more on that shortly!), which by default just requires an authenticated user. If you're not authenticated, you'll be redirected to the login page for Razor Pages apps (you'll receive a `401` response for APIs).

If you want to apply a different policy, you can specify one in the constructor of the `AuthorizeFilter`:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before

    // Pass a policy in the constructor of the Authorization filter
    services.AddRazorPages()
        .AddMvcOptions(options => options.Filters.Add(new AuthorizeFilter("MyCustomPolicy")));

    // Configure the custom policy
    services.AddAuthorization(options =>
    {
        options.AddPolicy("MyCustomPolicy",
            policyBuilder => policyBuilder.RequireClaim("SomeClaim"));
    });
}
```

The authorization filter is still applied globally, so users will always be required to login, but now they must also satisfy the `"MyCustomPolicy"` policy. If they don't, they'll be redirected to an access denied page for Razor Pages apps (or receive a `403` for APIs).

> Remember, this policy applies _globally_ so you need to ensure your "Login" and "AccessDenied" pages are decorated with `[AllowAnonymous]`, otherwise you'll end up with endless redirects.

Applying `AuthorizeFilter`s like this was the standard approach for early versions of ASP.NET Core, but ASP.NET Core 3.0 introduced [endpoint routing](https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/#the-evolution-of-routing). Endpoint routing allows moving some previously-MVC-only features to being first-class citizens. Authorization is one of those features!

## Using RequireAuthorization on endpoint definitions

The big problem with the `AuthorizeFilter` approach is that it's an MVC-only feature. ASP.NET Core 3.0+ provides a different mechanism for setting authorization on endpoints—the `RequireAuthorization()` extension method on `IEndpointConventionBuilder`.

Instead of configuring a global `AuthorizeFilter`, call `RequireAuthorization()` when configuring the endpoints of your application, in `Configure()`:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before

    // No need to add extra filters
    services.AddRazorPages();

    // Default authorization services
    services.AddAuthorization();
}

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        // Require Authorization for all your Razor Pages
        endpoints.MapRazorPages().RequireAuthorization();
    });
}
```

The net effect of this is the same as applying a global `AuthorizeFilter`. So why use this approach? One big advantage is the ability to add authorization for _other_ endpoints, that aren't MVC or Razor Pages. For example, you could require authenticated requests for your health check endpoints:

```csharp
app.UseEndpoints(endpoints =>
{
    // Require Authorization for all your Razor Pages
    endpoints.MapRazorPages().RequireAuthorization();
    // Also require authorization for your health check endpoints
    endpoints.MapHealthChecks("/healthz").RequireAuthorization();
});
```

As before, you can specify a different policy to apply in the call to `RequireAuthorization()`. You could also provide different policies to apply for different endpoints. In the example below I'm applying the `"MyCustomPolicy"` policy to the Razor Pages endpoints, and two policies, `"OtherPolicy"` and `"MainPolicy"` to the health check endpoints:

```csharp
app.UseEndpoints(endpoints =>
{
    // Require Authorization for all your Razor Pages
    endpoints.MapRazorPages().RequireAuthorization("MyCustomPolicy");
    // Also require authorization for your health check endpoints
    endpoints.MapHealthChecks("/healthz").RequireAuthorization("OtherPolicy", "MainPolicy");
});
```

> As always, ensure you've registered the policies in the call to `AddAuthorization()`, and ensure your added `[AllowAnonymous]` to your Login Access Denied pages.

If you don't provide a policy name in the `RequireAuthorization()` call, then the _DefaultPolicy_ is applied. This is the same behaviour as using an `[Authorize]` filter without a policy name.

## Changing the `DefaultPolicy` for an application

The _DefaultPolicy_ is the policy that is applied when:

- You specify that authorization is required, either using `RequireAuthorization()`, by applying an `AuthorizeFilter`, or by using the `[Authorize]` attribute on your actions/Razor Pages.
- You don't specify which policy to use.

Out-of-the-box, the _DefaultPolicy_ is configured as the following:

```csharp
new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();
```

That means if you're authenticated, then you're authorized. This provides the default behaviour that you're likely familiar with, of redirecting unauthenticated users to the login page, but allowing any authenticated user access to the page.

You can change the _DefaultPolicy_ so that an empty `[Authorize]` attribute applies a different policy in `UseAuthorization()`. For example, the following sets the _DefaultPolicy_ to a policy that requires users have the Claim `"SomeClaim"`.

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before
    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        // Configure the default policy
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireClaim("SomeClaim")
            .Build();

        // ...other policy configuration
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        // The default policy applies here, as no other policy set
        endpoints.MapRazorPages().RequireAuthorization();

        // DefaultPolicy not used, as OtherPolicy is provided
        endpoints.MapHealthChecks("/healthz").RequireAuthorization("OtherPolicy");

        // DefaultPolicy not applied, as authorization not required
        endpoints.MapHealthChecks("/ready");
    });
}
```

The example above shows when the _DefaultPolicy_ is applied, and when it isn't. The _DefaultPolicy_ only applies when you've request authorization _and_ you haven't specified a different policy. So it only applies to the Razor Pages endpoints in the example above.

Applying a default policy like this can be very useful, but sometimes you want to have slightly more granular control over when to apply policies. In Razor Pages applications for example, you might want to apply a given policy to one folder, and a different policy to another folder or area. You can achieve that using Razor Pages conventions.

## Applying authorization policies using conventions with Razor Pages

The Razor Pages framework is designed around a whole set of conventions that are designed to make it easy to quickly build applications. However, you can customise virtually all of those conventions when your app starts, and authorization is no different.

The Razor Page conventions allow you to set authorization requirements based on a folder, area, or page. They also allow you to mark sections and pages with `AllowAnonymous` in situations where you need to "punch a hole" through the default authorization policy. [The documentation on this feature is excellent](https://docs.microsoft.com/aspnet/core/security/authorization/razor-pages-authorization), so I've just provided a brief example below:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before

    // Applying multiple conventions 
    services.AddRazorPages(options => 
    {
        // These apply authorization policies to various folders and pages
        options.Conventions.AuthorizeAreaFolder("Users", "/Accounts");
        options.Conventions.AuthorizePage("/ChangePassword");

        // You can provide the policy as an optional parameter, otherwise the DefaultPolicy is used
        options.Conventions.AuthorizeFolder("/Management", "MyCustomPolicy"); 

        // You can also configure [AllowAnonymous] for pages/folders/areas
        options.Conventions.AllowAnonymousToAreaPage("Identity", "/Account/AccessDenied");
    });

    services.AddAuthorization(options =>
    {
        // ...other policy configuration
    });
}
```

These conventions can be useful for broadly applying authorization policies to whole sections of your application. But what if you just want to apply authorization _everywhere_? That's where the _FallbackPolicy_ comes in.

## Using the FallbackPolicy to authorize everything

The _FallbackPolicy_ is applied when the following is true:

- The endpoint does _not_ have any authorisation applied. No `[Authorize]` attribute, no `RequireAuthorization`, nothing.
- The endpoint does _not_ have an `[AllowAnonymous]` applied, either explicitly or using conventions.

So the `FallbackPolicy` only applies if you don't apply _any_ other sort of authorization policy, including the `DefaultPolicy`, When that's true, the _FallbackPolicy_ is used.

By default, the _FallbackPolicy_ is a no-op; it allows all requests _without_ authorization. You can change the _FallbackPolicy_ in the same way as the _DefaultPolicy_, in `UseAuthorization`:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...other config as before
    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        // Configure the default policy
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireClaim("SomeClaim")
            .Build();

        // ...other policy configuration
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        // The FallbackPolicy applies here, as no other policy set
        endpoints.MapRazorPages()

        // FallbackPolicy not used, as authorization applied
        endpoints.MapHealthChecks("/healthz").RequireAuthorization("OtherPolicy");

        // FallbackPolicy not used, as DefaultPolicy applied
        endpoints.MapHealthChecks("/ready").RequireAuthorization();
    });
}
```

In the example above, the _FallbackPolicy_ is set to a custom policy. It only applies to the Razor Pages endpoints, as the health check endpoints have specified authorization requirements of `"OtherPolicy"` and the _DefaultPolicy_.

With the combination of the _DefaultPolicy_, _FallbackPolicy_, Razor Page conventions, and the `RequireAuthorization()` extension method, you have multiple ways to apply authorization "globally" in your application. Remember that you can always override the _DefaultPolicy_ and _FallbackPolicy_ to achieve a more specific behaviour by applying an `[Authorize]` or `[AllowAnonymous]` attribute directly to a Razor Page or action method.

## Summary

In this post I described the various options available for setting global authorization policies:

- Apply an `AuthorizeFilter` globally. This is no longer the recommended approach, as it is limited to MVC/Razor Pages endpoints.
- Call `RequireAuthorization()` when configuring an endpoint in `UseEndpoints()`. You can specify a policy to apply, or leave blank to apply the _DefaultPolicy_.
- Apply authorization to Razor Pages using conventions when you call `AddRazorPages()`. You can apply authorization policies _and_ `[AllowAnonymous]` using these conventions.
- The _DefaultPolicy_ is applied when you specify that authorization is required, but don't specify a policy to apply. By default, the _DefaultPolicy_ authorizes all authenticated users.
- The _FallbackPolicy_ is applied when no authorization requirements are specified, including the `[Authorize]` attribute or equivalent. By default, the _FallbackPolicy_ does not apply any authorization.