Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I integration test a ASP 5/Core Web API with [Authorize] Attributes

I currently have an ASP 5/ASP Core Web API that I need to integration test with the OWIN Test Server.

The problem is that I use IdentityServer as the authorization server in production and I do not want to include the authorization as part of my integration testing.

This is the Startup.cs of the API:

public Startup(IHostingEnvironment env)
{
    // Set up configuration sources.
    IConfigurationBuilder builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true);

    if (env.IsEnvironment("Development"))
    {
        // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
        builder.AddApplicationInsightsSettings(developerMode: true);
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build().ReloadOnChanged("appsettings.json");
}

public IConfigurationRoot Configuration { get; set; }

// This method gets called by the runtime. Use this method to add services to the container
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    ConfigureEntityFrameworkDatabase(services, Configuration);

    services.AddIdentity<IdentityUser, IdentityRole>()
        .AddEntityFrameworkStores<HoehenSuchtIdentityDbContext>()
        .AddDefaultTokenProviders();

    ConfigureMvc(services);

    // register autofac as dependency resolver
    ContainerBuilder containerBuilder = new ContainerBuilder();

    // register all required autofac modules
    RegisterAutofacModules(containerBuilder);

    // register all automapper mappings as di services so there dependencies can be resolved
    ConfigureAutomapper(containerBuilder);

    ConfigureSwagger(services);

    // copy all asp core dependency injection registrations to autofac
    containerBuilder.Populate(services);
    IContainer container = containerBuilder.Build();

    return container.Resolve<IServiceProvider>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }

    // make sure the database was created and all migrations applied
    MigrateDatabase(app);
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().EnsureSeedData(env);

    app.UseIISPlatformHandler();

    app.UseApplicationInsightsRequestTelemetry();
    app.UseApplicationInsightsExceptionTelemetry();

    ConfigureIdentityServer(app, Configuration);

    app.UseStaticFiles();

    app.UseMvc();

    //app.UseSwaggerGen(/*routeTemplate: "docs/{apiVersion}/swagger.json"*/);
    //app.UseSwaggerUi(/*baseRoute: "docs", swaggerUrl: "docs/v1/swagger.json"*/);
}

public static Action<IServiceCollection, IConfigurationRoot> ConfigureEntityFrameworkDatabase = (services, config) =>
{
    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<HoehenSuchtDbContext>(builder =>
            builder.UseSqlServer(config["Data:DefaultConnection:ConnectionString"]))
        .AddDbContext<HoehenSuchtIdentityDbContext>(builder =>
            builder.UseSqlServer(config["Data:IdentityConnection:ConnectionString"]));
};

public static Action<IServiceCollection> ConfigureMvc = services =>
{
    services.AddMvc().AddControllersAsServices(new List<Assembly> { typeof(Startup).GetTypeInfo().Assembly });
};

I already tried registering a special test middleware that in theory should authenticate and set a claims principal. But somewhere down the OWIN pipeline the authentication is denied and I get a 401 error code.

This is how I setup the OWIN Test Server:

Startup.MigrateDatabase = app =>
{
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().Database.EnsureCreated();
};
Startup.ConfigureEntityFrameworkDatabase = ApiTestServer.ConfigureInMemoryDatabase;
Startup.ConfigureIdentityServer = (app, config) =>
{
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().EnsureSeedData(new HostingEnvironment {EnvironmentName = "development" });

    app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
};
Server = new TestServer(TestServer.CreateBuilder().UseStartup<Startup>());

And this is my custom AuthenticatedTestRequestMiddleware:

public class AuthenticatedTestRequestMiddleware
{
    public const string TestingCookieAuthentication = "TestCookieAuthentication";
    public const string TestingHeader = "X-Integration-Testing";
    public const string TestingHeaderValue = "78EAAA45-E68B-43C7-9D12-3A5F1E646BD5";

    private readonly RequestDelegate _next;

    public AuthenticatedTestRequestMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Headers.Keys.Contains(TestingHeader) && context.Request.Headers[TestingHeader].First().Equals(TestingHeaderValue))
        {
            // fake authenticated the user
            ClaimsIdentity claimsIdentity = new ClaimsIdentity();
            claimsIdentity.AddClaims(new List<Claim>
            {
                new Claim(ClaimTypes.Name, "admin"),
                new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
            });
            ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
            context.User = claimsPrincipal;
        }

        await _next(context);
    }
}

The principal is set and exists in the database with the given ID, but after I call next(context) I get an 401 Unauthorized result.

How can I successfully fake authenticate the user and bypass the [Authorize] while also setting the current User for the HttpRequest?

UPDATE: If I register my own CookieAuthentication handler like that:

app.UseCookieAuthentication(options =>
{
    options.AuthenticationScheme = AuthenticatedTestRequestMiddleware.TestingCookieAuthentication;
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
});

I get the 302 Redirect to the login page. The signin however is working correctly when I use this inside the TestMiddleware await context.Authentication.SignInAsync(TestingCookieAuthentication, claimsPrincipal)

like image 736
Silthus Avatar asked May 14 '16 06:05

Silthus


People also ask

What is integration test in ASP NET Core?

Thank you. Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, such as the database, file system, and network. ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server.

How to add integration tests for API project in Visual Studio?

In order to add integration tests for API project, follow these steps: Right click on Solution > Add > New Project Go to Installed > Visual C# > Test > xUnit Test Project (.NET Core) Set the name for project as WideWorldImporters.API.IntegrationTests Click OK Manage references for WideWorldImporters.API.IntegrationTests project:

What is MVC testing in ASP NET Core?

The Microsoft.AspNetCore.Mvc.Testing package handles the following tasks: Copies the dependencies file (*.deps) from the SUT into the test project's bin directory. Sets the content root to the SUT's project root so that static files and pages/views are found when the tests are executed.

How to get started with integration testing?

So lets get started. Integration test is the phase of software testing, which is usually done after the unit testing phase. And one of the pre-requisite for the integration test, is that all the necessary components required for the functionality that we will have to test, should be available.


Video Answer


1 Answers

Ok so I found out why it does not work :)

When creating the ClaimsPrincipal the AuthenticationProvider must be included in the constructor of the principal. If the authentication type is not provided the SignInAsync() function will fail and not authenticated the user.

Instead of doing this:

ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
    new Claim(ClaimTypes.Name, "admin"),
    new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
});

You must specify the AuthenticationHandler like this:

ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
    new Claim(ClaimTypes.Name, "admin"),
    new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
}, TestingCookieAuthentication);
like image 62
Silthus Avatar answered Oct 18 '22 23:10

Silthus