I have the following setup where I have two potential authentication schemes in my web api. They are used for different controllers and use a different auth method. I define them here:
//startup.cs
services.AddAuthentication(options =>
{
options.DefaultScheme = MarsAuthConstants.Scheme;
})
.AddScheme<MarsAuthSchemeOptions, MarsAuthHandler>(MarsAuthConstants.Scheme, options =>
{
})
.AddScheme<MarsProjectAuthSchemeOptions, MarsProjectAuthHandler>(MarsAuthConstants.ProjectScheme, options =>
{
});
The scheme's names are "Mars" and "MarsProject" respectively.
I'm using Microsoft's instructions to set up mock authentication scheme in my integration tests like so:
[Fact]
public async Task GetProject_Http_Test()
{
var client = Factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test";
options.DefaultChallengeScheme = "Test";
}).AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"Test", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
var res = await client.GetAsync("/api/projects/" ProjectId);
res.StatusCode.Should().NotBe(HttpStatusCode.Unauthorized);
res.StatusCode.Should().Be(HttpStatusCode.OK);
}
However, this test is failing because it's returning 401 Unauthorized. When I debug through, I see that even though I'm configuring the "Test" authorization scheme, it's still calling my MarsAuthHandler
.
Since I have 2 controllers that need to be authenticated differently, I have the to specify [Authorize(scheme)]
on them, e.g.:
[HttpGet]
[Route("{id}")]
[Authorize(AuthenticationSchemes = MarsAuthConstants.ProjectScheme)]
public async Task<ProjectView> GetProjectAsync(long id)
{
return await _presentationService.GetProjectAsync(id);
}
If I remove the [Authorize]
then my test passes. However, I need this because I cannot specify a single default auth scheme for my entire application.
How can I bypass the [Authorize]
attribute when using the Test auth scheme so that my tests can bypass the normal auth methodology?
CodePudding user response:
Since your authorize attribute explicitly using the MarsAuthConstants.ProjectScheme
, there is no built-in "easy" way to mock it.
- There is no "magic" inside
ConfigureTestServices
, it simply adds additional service configuration for mock dependencies.
So, you have several options:
- Override (hacky) the original schema handler with your test handler
[Fact]
public async Task GetProject_Http_Test()
{
var client = Factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(options =>
{
var authSchemeBuilderMars = new AuthenticationSchemeBuilder("Mars");
authSchemeBuilderMars.HandlerType = typeof(TestAuthHandler);
var authSchemeBuilderMarsProject = new AuthenticationSchemeBuilder("MarsProject");
authSchemeBuilderMarsProject.HandlerType = typeof(TestAuthHandler);
// Override already registered schemas
o.Schemes.Where(s => s.Name == "Mars").First().HandlerType = typeof(TestAuthHandler);
o.Schemes.Where(s => s.Name == "MarsProject").First().HandlerType = typeof(TestAuthHandler);
o.SchemeMap["Mars"] = authSchemeBuilderMars;
o.SchemeMap["MarsProject"] = authSchemeBuilderMarsProject;
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
var res = await client.GetAsync("/api/projects/" ProjectId);
res.StatusCode.Should().NotBe(HttpStatusCode.Unauthorized);
res.StatusCode.Should().Be(HttpStatusCode.OK);
}
- Use authorization policies. Fortunately, they can be overridden:
In your code setup (example for Mars scheme):
builder.Services.AddAuthorization(options =>
options.AddPolicy("MarsPolicy", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AuthenticationSchemes.Add("Mars");
}));
In your controller:
[Authorize(Policy = "MarsPolicy")]
In your test code:
[Fact]
public async Task GetProject_Http_Test()
{
var client = Factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test";
options.DefaultChallengeScheme = "Test";
}).AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"Test", options => { });
});
// This will override the 'MarsPolicy' policy
services.AddAuthorization(options =>
options.AddPolicy("MarsPolicy", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AuthenticationSchemes.Add("Test");
}));
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
var res = await client.GetAsync("/api/projects/" ProjectId);
res.StatusCode.Should().NotBe(HttpStatusCode.Unauthorized);
res.StatusCode.Should().Be(HttpStatusCode.OK);
}