Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core 3 mock authorization during integration testing

I am using ASP.NET core to build an API, and I am trying to upgrade from .NET core 2.2 to .NET core 3.1.

I am using the [Authorize] attribute to secure the API endpoints and I want to bypass it during integration tests.

I have managed to to create a custom AuthenticationHandler that authenticates a fake user, and an authorization handler that authorizes anybody (including anonymous users).

My problem is that the user injected in the Authentication handler is not propagated to the Authorization handler and the filter DenyAnonymousAuthorizationRequirement fails because the User in the context is null.

Has anyone dealt with something similar?

By the way, the class DenyAnonymousAuthorizationRequirement is a Microsoft class, I copied the code as it appeared in the IDE just for the post here.

My custom AuthenticationHandler:

  public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
  {
        public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
                               ILoggerFactory logger,
                               UrlEncoder encoder,
                               ISystemClock clock)
            : base(options, logger, encoder, clock) { }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            ClaimsPrincipal fakeUser = FakeUserUtil.FakeUser();
            var ticket = new AuthenticationTicket(fakeUser, "Test");
            var result = AuthenticateResult.Success(ticket);
            return Task.FromResult(result);
        }
  }

The AuthorizationRequirement:

public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
    {

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
        {
            var user = context.User;
            var userIsAnonymous =
                user?.Identity == null ||
                !user.Identities.Any(i => i.IsAuthenticated);
            if (!userIsAnonymous)
            {
                context.Succeed(requirement);
            }
            return Task.CompletedTask;
        }
    }

The code in the ConfigureServices method that uses the above classes:

 public void ConfigureServices(IServiceCollection services)
 {

            services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", null);
            services.AddAuthorization(configure =>
            {
                var builder = new AuthorizationPolicyBuilder(new List<string> {"Test"}.ToArray())
                    .AddRequirements(new DenyAnonymousAuthorizationRequirement());
                configure.DefaultPolicy = builder.Build();
            });
            services.AddControllers()
                    .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
                    .AddNewtonsoftJson(options =>
                                           options.SerializerSettings.ReferenceLoopHandling =
                                               ReferenceLoopHandling.Ignore);

}
like image 277
orestis Avatar asked Jan 03 '20 11:01

orestis


People also ask

Should mocks be used in integration tests?

In integration testing, the rules are different from unit tests. Here, you should only test the implementation and functionality that you have the control to edit. Mocks and stubs can be used for this purpose.

How do I Authorize a user in .NET Core?

Add the UseAuthentication middleware after UseRouting in the Configure method in the Startup file. This will enable us to authenticate using ASP.NET Core Identity. With all of this in place, the application Is all set to start using Identity.

What is mocking in integration testing?

Mocking in programming refers to an action of substituting a part of the software with its fake counterpart. Mocking technique is primarily used during testing, as it allows us to take out certain aspects of the tested system, thus narrowing the test's focus and decreasing the test's complexity.

What is WebApplicationFactory?

WebApplicationFactory<TEntryPoint> is used to create a TestServer for the integration tests. TEntryPoint is the entry point class of the SUT, usually Program.


2 Answers

Have a related problem and have stumbled on this article here

it shows that you can override the user that is set in the HttpContext this way:

class FakeUserFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, "123"),
            new Claim(ClaimTypes.Name, "Test user"),
            new Claim(ClaimTypes.Email, "[email protected]"),
            new Claim(ClaimTypes.Role, "Admin")
        }));

        await next();
    }
}

and then inject it in ConfigureTestServices:

builder.ConfigureTestServices(services =>
        {
            services.AddMvc(options =>
            {                   
                options.Filters.Add(new AllowAnonymousFilter());
                options.Filters.Add(new FakeUserFilter());
            })
            .AddApplicationPart(typeof(Startup).Assembly);
        });

Hope this helps

like image 76
248oleg248 Avatar answered Oct 21 '22 01:10

248oleg248


Check out this documentation https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1 at "Mock Authentication" session. I think that you need to add this code after create you client:

client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Test");
like image 2
Matheus Xavier Avatar answered Oct 21 '22 03:10

Matheus Xavier