#software-development #dotnet #graphql
Please note before reading: this post flows over from implementation details of the graphql-dotnet project. If you’re like me, stuck on authorization for subscriptions, and want to know how I worked around it, read the post. If you just want authorization with subscriptions to work, copy past the code blocks.
The folks working on the graphql-dotnet library have done some amazing work on bringing the GraphQL specification to the .NET ecosystem. While the library is definitely not easy to implement, the resulting GraphQL API can be a delight to work with.
By now several additional libraries have been developed. Notoriously the authorization library. This library brings policies and permissions to your GraphQL API. This authorization library is not dependent on any specific authorization mechanism, as long as you can provide an implementation for the IProvideClaimsPrincipal
interface which sole responsibility is providing GraphQL with a ClaimsPrincipal
instance. Validation logic is provided by implementing the IValidationRule
interface.
While this works fine for queries and mutations, at the time of writing the following issues with regards to making bearer authorization work on subscriptions:
- HTTP headers are provided in the payload of the first frame that goes over the WebSocket connection. This first frame has the type of
connection_init
. As the subscription subscriber is only invoked at thestart
frame, it might seem difficult at first sight to retrieve the JWT token from the payload of theconnection_init
frame.
If you want to know what is going on within a websocket connection, the developer tools inside browsers can show the individual frames on websocket connections these days, which is a nice way of getting to know some about the protocols flowing through :)
- Subscriptions hijack the
UserContext
property on theResolveEventStream
object to pass aMessageHandlingContext
instance. Therefore, generic auth validation logic provided with anIValidationRule
implementation cannot access the ClaimsPrincipal object on theUserContext
, therefore failing verification.
Retrieving the bearer token
While the GraphQL library looks pretty daunting at first sight, it also happens to be incredibly extensible at all points through the use of interfaces. One of these extension points happens to be the IOperationMessageListener
, which acts on different messages received via the websocket connections, and therefore, indirectly on subscriptions. I have implemented the IOperationMessageListener
in a way that the bearer token can be extracted from the connection_init
frame.
public class SubscriptionInitializer : IOperationMessageListener
{
private IHttpContextAccessor _contextAccessor;
public SubscriptionInitializer(IHttpContextAccessor httpContextAccessor)
{
_contextAccessor = httpContextAccessor;
}
public async Task BeforeHandleAsync(MessageHandlingContext context)
{
if (context.Terminated) return;
var message = context.Message;
if (message.Type == MessageType.GQL_CONNECTION_INIT)
{
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(message.Payload.GetValue("Authorization").ToString().Replace("Bearer ", ""),
new TokenValidationParameters
{
ValidIssuer = "TODO: Enter your issuer",
ValidAudience = "TODO: Enter your audience",
IssuerSigningKey = new JsonWebKey(@"TODO: Load your JWKS")
},
out SecurityToken token);
if (!user.Identity.IsAuthenticated) await context.Terminate();
_contextAccessor.HttpContext.User = user;
}
}
public Task HandleAsync(MessageHandlingContext context)
{
return Task.CompletedTask;
}
public Task AfterHandleAsync(MessageHandlingContext context)
{
return Task.CompletedTask;
}
}
Passing on the IPrincipal
As seen in the code above it is not too difficult to validate a JWT for a subscription, but what if we’re using the authorization library and want our existing IValidationRule
implementations also to apply for subscriptions?
To understand one of the possible ways we can pass this data to the IValidationRule
implementations we have to dig into the DI system. The GraphQL library heavily relies on the DI mechanics, and the only thing we want to know is how IValidationRule
objects are passed, or in this case, injected.
You don’t have to figure that out yourself. A type of IEnumerable<IValidationRule>
is injected into the constructor of the DefaultGraphQLExecuter<TSchema>
which is being registered in the DI as transient, therefore meaning that it is instantiated every time it is requested. Thankfully the IGraphQLExecuter<TSchema>
is requested in the GraphQLHttpMiddleware<TSchema>
middleware, so with every request we get a new executer. Great!
Because of these properties we can create our own object which is injected through the constructors both in the IOperationMessageListener
and IValidationRule
implementations. We will transfer the principal by means of this transport object. We can then populate the ClaimsPrincipal
in the UserContext
with the value we passed in this terrible, awefull, and just plain ugly way, but at least it’s better than duplicating code multiple times.
Now here comes the exciting part (As inspired by this commented out class, which I only discovered after I figured everything else out…). We can inject the IHttpContextAccessor
into our class to have the ability to get the current HttpContext
instance into our SubscriptionInitializer
. Even more beautifull is the way we can pass the ClaimsPrincipal
to the IValidationRule
: We set the HttpContext.User
property with our ClaimsPrincipal
, after which it is available on the current HttpContext
. How easy can life be sometimes.
Also, don’t forget to inject the IHttpContextAccessor
into your IValidationRule
in order to be able to access the IPrincipal
.