ASP.NET Core disable authentication in development environment

C#asp.net CoreAuthentication

C# Problem Overview


Is it possible to "disable" authentication in ASP.NET Core application without changing its logic?

I have a .net website which uses an external identity server app for authentication. Anyway I would like to be able to mock the authentication when I'm developing it (ASPNETCORE_ENVIRONMENT = Development), airing access to all actions ignoring the authorization attributes.

Is it possible to do it just mocking some services in the service collection?

C# Solutions


Solution 1 - C#

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!

Solution 2 - C#

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.

Solution 3 - C#

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>();
        });
    }
}

Solution 4 - C#

I've found sollution for this problem on illucIT Blog.

This code must work:

if (env.IsDevelopment()) {
   services.AddMvc(opts =>
   {
      opts.Filters.Add(new AllowAnonymousFilter());
   });
} else {
   services.AddMvc();
}

Solution 5 - C#

It's tricky to give a detailed answer without more details on your end, but I have previously achieved this by conditionally registering:

  • the external authentication middleware
  • the global policy that requires an authenticated request

it looked something like:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        Environment = env;
    }

    public IHostingEnvironment Environment { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(x =>
        {
            if (!Environment.IsDevelopment())
            {
                var authenticatedUserPolicy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();

                x.Filters.Add(new AuthorizeFilter(authenticatedUserPolicy));
            }
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseStaticFiles();

        if (!Environment.IsDevelopment())
        {
            // Register external authentication middleware
        }

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

In my case, the authorization filter was applied globally, so every single action of the MVC app required an authenticated user.

If you have different requirements - fine-grained [Authorize] attributes on some actions - then you could probably achieve the same result by changing how the associated authorization policies are built. They could basically contain no requirements at all.

AuthorizationPolicy yourCustomPolicy = null;
if (Environment.IsDevelopment())
{
    yourCustomPolicy = new AuthorizationPolicyBuilder().Build();
}
else
{
    yourCustomPolicy = new AuthorizationPolicyBuilder()
        // chaining appropriate methods to suit your needs
        .Build();
}

Solution 6 - C#

This is to clarify @Kirill Lutsenko's answer about the method he found on the IllucIT blog post (note that in my case this is for .NET Core 2.0. I see other answers saying the AllowAnonymousFilter method won't work in .NET Core 3.1):

The Startup class has an overloaded constructor. One of the overloads takes an IHostingEnvironment parameter. You need to use this version of the constructor.

In the Startup class create a property of type IHostingEnvironment. Call it, say, Environment. Then set that property in the constructor.

Then, in the ConfigureServices method, you can use Environment.IsDevelopment().

public class Startup
{        
    public Startup(IHostingEnvironment environment)
    {
        Environment = environment;
    }
	
    public IHostingEnvironment Environment { get; }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        //...

        services.AddMvc(options =>
        {
            // This uses the Environment property populated in the constructor.
            if (Environment.IsDevelopment())
            {
                options.Filters.Add(new AllowAnonymousFilter());
            }
            
            // Set other options here.  For example:
            options.ModelBinderProviders.Insert(0, new UTCDateTimeModelBinderProvider());
            //...
        });
        
        //...
    }
}

As a side note, in real life we use a different overload of the constructor, which takes both an IConfiguration object and an IHostingEnvironment object as parameters. That allows us to configure services based on an appsettings.json configuration file.

For example:

public class Startup
{        
    public Startup(IConfiguration configuration, IHostingEnvironment environment)
    {
        Configuration = configuration;
        Environment = environment;
    }
	
    public IConfiguration Configuration { get; }
    public IHostingEnvironment Environment { get; }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        //...

        // Data access via Entity Framework
        services.AddDbContext<ContainersDbContext>(options =>
        {
            options.UseNpgsql(Configuration.GetConnectionString("OrdersDatabase"));
        });
        
        //...
    }
}

Solution 7 - C#

In ASP.NET Core 6, we managed to disable the authorization without changing any other part from the productive code, just the following logic in Program.cs:

if (!builder.Environment.IsDevelopment())
{
    app.MapControllers();
}
else
{
    app.MapControllers().AllowAnonymous();
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfraView Question on Stackoverflow
Solution 1 - C#Simon HooperView Answer on Stackoverflow
Solution 2 - C#roxtonView Answer on Stackoverflow
Solution 3 - C#ozzyView Answer on Stackoverflow
Solution 4 - C#Kirill LutsenkoView Answer on Stackoverflow
Solution 5 - C#Mickaël DerrieyView Answer on Stackoverflow
Solution 6 - C#Simon TewsiView Answer on Stackoverflow
Solution 7 - C#Cristina AlboniView Answer on Stackoverflow