Enterprises continue to move from big monoliths to a microservice oriented architecture. Of course, this has lots of advantages over having one or more big monoliths in terms of scalability, maintainability and so forth. When moving to a microservice oriented architecture, one obstacle to overcome is identity authentication and authorization.
Most of the time, the propagation of the end user security context tends to stop at the API gateway layer, and a more generic security mechanism such as a service account, with basic authentication for example, steps in to secure traffic between microservices. In that case there is no way of telling if an end user is allowed to access a specific resource. This also introduces the risk of resources getting exposed due to service accounts which might have super-user privileges. So, a better solution would be to propagate the end user security context to every microservice that is being called during the lifetime of the request.
Securing each step
Typically, in such an architecture you would have one or multiple identity providers which can issue tokens to end users. Usually this is done using OpenID Connect, OAuth or JSON Web Tokens. Either way, these tokens are usually put in the Authorization
header of the request. When the request is received by the first microservice, it would check if this token is a valid token at the identity provider and otherwise throw a HTTP 401/403 if not.
When that microservice has to make a request to another microservice to fetch additional data to construct the resource, the token has to be propagated to that other microservice as well. Most of the time the token is fetched from the authorization header at controller level and passed on to the method making the call to the next microservice. However, this makes the code completely dependent on passing tokens which you most likely don't want. To overcome this problem I've made a DelegatingHandler
which can be used by any instance of HttpClient
:
public class AuthorizationHeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthorizationHeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authHeader))
{
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authHeader);
}
return base.SendAsync(request, cancellationToken);
}
}
The AuthorizationHeaderHandler
will propagate the incoming token to request that is made using the HttpClient
that implements this handler. To get that token we have to access the current HttpContext
. Because we can't access HttpContext
in .NET Core outside a controller, there is a helper class called HttpContextAccessor
which we can use to access the current HttpContext
. The AuthorizationHeaderHandler
uses this helper class to get the authorization token from the current request headers and put it on the outgoing request just before it is send.
Now to add this delegating handler to any HttpClient
, using the new HttpClientFactory
that is available as of .NET Core 2, can be done in like so:
services.AddHttpContextAccessor();
services.TryAddTransient<AuthorizationHeaderHandler>();
services.AddHttpClient("AuthorizedClient")
.AddHttpMessageHandler<AuthorizationHeaderHandler>();
For this example i've used a named HttpClient
. The AuthorizationHeaderHandler
has to be added as Transient
dependency in order for it to be available to the HttpClientFactory
. Note that there is an extension method available to add the HttpContextAccessor
dependency.
When making a request to another microservice, for example:
public class Foo
{
private readonly IHttpClientFactory _httpClientFactory;
public Foo(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<string> Bar()
{
var client = _httpClientFactory.CreateClient("AuthorizedClient");
return await client.GetStringAsync("http://somemicroserviceurl");
}
}
The authorization header is automatically set on every request that is made using the HttpClient
from the HttpClientFactory
. This way the end user security context can be propagated in a clean way instead of passing tokens everywhere and making your code dependent on it.
Check out the full source code at GitHub.