How To Deal With Identity When Testing An ASP.NET Core Application
User identity is a collection of security information associated to an authenticated user. It is used to implement authorization mechanisms with the aim of protecting application resources from unauthorized accesses.
In ASP.NET Core, user identity and the related authorizations resolutions are performed through high level middlewares.
Middlewares are elements of the application pipeline through which requests and responses pass to apply a specific logic. They are intended to design multi-layer requests/responses handling systems.
Here is a middleware pipeline schema from the ASP.NET Core Microsoft documentation :
My serie about Testing ASP.NET Core Applications has started here and several subjects have been covered like Behavior Approach, Configuration, Hosting, Model Testing, Dependencies Mocking, etc. But what about Identity and how to deal with authentication and autorization processes when testing an ASP.NET Core application?
Creation Of A Test Authentication Middleware
As I said before, in ASP.NET Core, User Identity and the relatives authentication systems are implemented through middlewares with a dedicated object model. We can so use this object model to develop our own Authentication Middleware to mock the standard one.
All authentication middlewares in ASP.NET Core have to define a set of options and a handler which are respectively represented by the AuthenticationOptions
and the AuthenticationHandler
classes.
Authentication Option
Let's here configure our authentication middleware for tests purposes:
public class TestAuthenticationOptions : AuthenticationOptions
{
public virtual ClaimsIdentity Identity { get; } = new ClaimsIdentity(new Claim[]
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Guid.NewGuid().ToString()),
new Claim("http://schemas.microsoft.com/identity/claims/tenantid", "test"),
new Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", Guid.NewGuid().ToString()),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", "test"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", "test"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", "test"),
}, "test");
public TestAuthenticationOptions()
{
this.AuthenticationScheme = "TestAuthenticationMiddleware";
this.AutomaticAuthenticate = true;
}
}
The ClaimsIdentity
property will hold the identity information we want to test within a scenario test. Here, we have defined a set of claims which represent the information we want to link to the test identity. Feel free to add or remove one following your needs.
I have defined this property as virtual to be able to override Identity information in order to adapt the test case.
We have also activated the AutomaticAuthenticate
property because we want our middleware to modify the request automatically.
Authentication Handler
The Authentication Handler provides the logic to execute while handling an authentication. For our needs, it will be very simple: the handler will always return an authentication success result containing the user identity holded by the options we have previously defined:
public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authenticationTicket = new AuthenticationTicket(
new ClaimsPrincipal(Options.Identity),
new AuthenticationProperties(),
this.Options.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
Authentication middleware
The authentication middleware will simply define how to handle a request in a test context using our test authentication handler:
public class TestAuthenticationMiddleware : AuthenticationMiddleware<TestAuthenticationOptions>
{
private readonly RequestDelegate next;
public TestAuthenticationMiddleware(RequestDelegate next, IOptions<TestAuthenticationOptions> options, ILoggerFactory loggerFactory)
: base(next, options, loggerFactory, UrlEncoder.Default)
{
this.next = next;
}
protected override AuthenticationHandler<TestAuthenticationOptions> CreateHandler()
{
return new TestAuthenticationHandler();
}
}
Application Configuration
The configuration of an ASP.NET Core application is defined within the Startup
class. Obviously, we only have one Startup
class and we also want to only use our TestAuthenticationMiddleware
while running tests.
So inside of the Startup's Configure
method, let's use the EnvironmentName configuration property of the hosting environment to parameter the middleware configuration:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.EnvironmentName == "Test")
{
app.UseMiddleware<TestAuthenticationMiddleware>();
}
...
}
Test With Identity
We can now write this kind of test that will be ran using the previously configured identity (please note the use of the UseEnvironment
method so that our previous condition is evaluated to true
and our TestAuthenticationMiddleWare
is used):
[TestClass]
public class BlogTest
{
[TestMethod]
public void TestBlog()
{
Data[] data = CreateTestScenarioDataIntoInMemoryDb();
var webHostBuilder = new WebHostBuilder().UseEnvironment("Test").UseStartup<Startup>()
var testServer = new TestServer(webHostBuilder);
var response = testServer.CreateClient().GetAsync("/api/data").Result;
response.EnsureSuccessStatusCode();
var results = response.Content.ReadAsAsync<Data[]>().Result;
for (int i = 0; i < data.Length; i++)
{
Assert.AreEqual(data[i], result[i]);
}
}
}
If you want more information about ASP.NET Core Testing, please take a look at my previous posts:
- A Different Approach To Test Your ASP.NET Core Application
- How To Test Your Asp.net Core MVC Stack
- Asp.Net Core MVC Testing And The Synchronizer Token Pattern
- ASP.NET Core Application Testing With SpecFlow
- TESTAVIOR: A Happy Solution To Test Your ASP.NET Core Applications
And feel free to contact me ;)