Reputation: 11
I have searched lots of posts here regarding my problem but have not found anything related to what I am trying to achieve.
I have created a Web API micro service for handling user authentication. The service uses identity framework and issues bearer tokens. I have another Web API which is secured using the [Authroize] attribute on the controllers. Both services talk to the same database via Entity Framework.
One hurdle I had to overcome whilst setting up this infrastructure was both services require the same machine key in their web.configs. When everything is deployed it works great.
However, I am trying to write some integration tests for my main API using the Microsoft Owin TestServer. This allows me to write integration tests against an in memory copy of the API which also uses an in memory copy of the database using the entity framework migrations.
I have successfully created integration tests for my user service. However, I am struggling to get integration tests working for my main service. I have created a solution with a new test project in it. I have also linked in my user service and main service. In the bootstrap for the integration tests, I spin up an in memory copy of each service by using each services Startup.cs.
Everything works ok until I try to hit a controller that is secured with the [Authorize] attribute. I have successfully hit the user service to register a user, activate that user and obtain the bearer token. I am then passing this over in the request to the main service in the auth header. However, I always an Unauthorized response from the service.
I suspect this is related to machine keys again and have tried putting the same machine key into an App.config within the integration test project. This did not make any difference.
If anyone has managed to get this scenario working I would very much appreciate any advice on what could be causing the problem. I haven't pasted any code as there is quite a lot involved but would be happy to paste any specifics.
Testing in this way is super fast and really powerful. However, there seems to be a lack of info out there on getting this working.
EDIT
Here is some code as requested. The two in memory APIs are created in a OneTimeSetUp as follows:
_userApi = TestServer.Create<UserApi.Startup>();
_webApi = TestServer.Create<WebApi.Startup>();
The Authorized HttpClient is then taken from the API as follows:
var client = _webApi.HttpClient;
var authorizationValue = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Authorization = authorizationValue;
"token" is obtained from the user service.
The GET is then performed using the client as follows:
var result = await client.GetAsync(uri);
EDIT 2
I should also point out that I have authenticated integration tests working within the User Service. The problem is only when I try to use both services together. E.g. Obtain token from User Service and use that token to authenticate against my main service. The in memory entity framework database is created as follows:
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(TestContext.CurrentContext.TestDirectory, string.Empty));
using (new ContractManagerDatabase())
{
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<ContractManagerDatabase>());
}
Upvotes: 0
Views: 1014
Reputation: 11
So after days of battling with this I have finally found a solution. The first thing I did was to move the integration tests back into the main Web API. Having taken a step back and thinking more about the problem I realised that by creating a separate solution and spinning up my user service and Web API in memory all I was really testing was Identity Framework and not the API itself.
I have integration tests working just fine in my user service for both unauthenticated and authenticated calls. It seemed to me that I should be able to do the same with the Web API and not rely on the user service.
I thought that I must be able to "bypass" the [Authorize] attribute to test the API. After some google searching, I found the following article which was most valuable in getting the solution working:
The key things for me were as follows:
The http client needed to be constructed differently for the test.
var client = new HttpClient(_webApp.Handler) {BaseAddress = new Uri("http://localhost")};
The call to GET had to be constructed differently for the test.
token = token == string.Empty ? GenerateToken(userName, userId) : token;
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("Authorization", "Bearer " + token);
var client = GetAuthenticatedClient(token);
return await client.SendAsync(request);
The token needs to be generated as follows. I also needed to add a claim for the User Id as this is taken from the request within the controller.
private static string GenerateToken(string userName, int userId)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.NameIdentifier, userId.ToString())
};
var identity = new ClaimsIdentity(claims, "Test");
var properties = new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddHours(1) };
var ticket = new AuthenticationTicket(identity, properties);
var format = new TicketDataFormat(_dataProtector);
var token = format.Protect(ticket);
return token;
}
I hope this post helps others with similar issues and thanks to everyone who contributed.
Upvotes: 1