ASP.NET Core disable authentication in development environment

On updating to net core 3.1, the mvc AllowAnonymousFilter was not working for us any more. We found conditionally adding a custom IAuthorizationHander to be the simplest way forward to conditionally bypass auth.

eg.

/// <summary>
/// This authorisation handler will bypass all requirements
/// </summary>
public class AllowAnonymous : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
            context.Succeed(requirement); //Simply pass all requirements
        
        return Task.CompletedTask;
    }
}

Then register this handler conditionally in Startup.ConfigureServices.

private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
    _env = env;
}

public void ConfigureServices(IServiceCollection services)
{
  {...}

  //Allows auth to be bypassed
  if (_env.IsDevelopment())
    services.AddSingleton<IAuthorizationHandler, AllowAnonymous>();
}

Note AddAuthentication and AddAuthorization services are still registered and configured as per prod code (which is nice).

To allow our unit test to bypass auth, we added a new anonymous testbase with a startup class that added this line without any conditions. Nice and simple!


In ASP.NET Core 3.x and later, you can bypass authorization in development environment by applying AllowAnonymousAttribute to your endpoints using the WithMetadata extension method.


Example 1. Apply AllowAnonymousAttribute to controllers in Program.cs in ASP.NET Core 6

if (app.Environment.IsDevelopment())
    app.MapControllers().WithMetadata(new AllowAnonymousAttribute());
else
    app.MapControllers();

...or in Startup.Configure() in ASP.NET Core 3.0 and later

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //...
    app.UseEndpoints(endpoints =>
    {
        if (env.IsDevelopment())
            endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
        else
            endpoints.MapControllers();
    });
}

Note that this will apply AllowAnonymousAttribute to all controllers.


Example 2. Apply AllowAnonymousAttribute to a minimal API endpoint in ASP.NET Core 6

var hiEndpoint = app
    .MapGet("/hi", () => "Hello!")
    .WithMetadata(new AuthorizeAttribute());

if (app.Environment.IsDevelopment())
    hiEndpoint.WithMetadata(new AllowAnonymousAttribute());

Details

endpoints and app from the examples above, both implement IEndpointRouteBuilder which has multiple Map extension methods like MapControllers() and MapGet(...) that return IEndpointConventionBuilder.

WithMetadata is an extension for IEndpointConventionBuilder and can be called upon the results of those Map methods.

AllowAnonymousAttribute's description from the docs:

Specifies that the class or method that this attribute is applied to does not require authorization.


Another solution you may want to consider is using the IPolicyEvaluator. This means that you can keep all the existing security elements.

public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
    public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
    {
        // Always pass authentication.
        var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), JwtBearerDefaults.AuthenticationScheme);
        return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
    }

    public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
    {
        // Always pass authorization
        return await Task.FromResult(PolicyAuthorizationResult.Success());
    }
}

In the Startup.cs, ensure this appears at the top of the ConfigureServices method. Eg.

    public void ConfigureServices(IServiceCollection services)
    {
        if (env.IsDevelopment())
        {
            // Disable authentication and authorization.
            services.TryAddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
        }
        ...

Rather than Startup.cs (and thanks to the comments below) if you are using Core 3.1 and you wish to use the WebApplicationFactory, you can do the following:

public class MyWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            // Disable Authentication.
            services.RemoveAll<IPolicyEvaluator>();
            services.AddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
        });
    }
}