Basic authentication

How to build a GraphQL API with authentication

This guide covers writing custom middleware for authentication purposes.

Overview

You can tell code.store to pass every request (query or mutation) marked with @auth directive via your custom middleware. In a nutshell, it looks like the following. Imagine, that you have two queries:

# This directive has to be added to the top of your GraphQL schema
directive @auth on FIELD_DEFINITION

type Query {
    allPosts: [Post]
    helloWorld: String! @auth
}

Here is what happens on each request to one of those queries:

All queries/mutation marked by @auth will pass via auth.handler.ts first

What happens is that all requests marked by @auth directive will pass via auth.handler.ts first and the context argument passed to the subsequent resolver is going to be concatenated with the return result of auth.handler:

The return result of auth.handler.ts is getting injected into the _context_ argument of subsequent resolvers.

That's not very complicated so let's build a simple example using this information!

Very simple example

For the sake of simplicity, we are going to use the code from our Quick Start guide but essentially any other working service (including the newly created one) will suffice.

Let's take a look at our schema first:

As you may see, we have restored the helloWorld query, we also have restored the src/resolvers/queries/helloWorld.ts file too.

Let's modify our schema by adding the directive declaration and by marking one of the requests as @auth:

There are two things that happened here:

  1. We added a declaration of the directive to the top of our schema.

  2. We marked two of our requests (query allPosts and mutation createPost) with @auth directive, which means that we will be able to create and see the posts only as authenticated users. Neat!

At the moment, this @auth directive won't do anything, so let's add middleware and implement a very basic authentication model.

First, generate the handler file by running the following command in your service directory:

This is what you will see inside the file (I removed the comments to save screen-space):

The typical authentication flow is like the following:

  • the client (your front-end or another service) will send a GraphQL request by passing some sort of authorization token in a specific header (typically it would be 'Authorization' header);

  • your middleware will parse the token and validate it (against another service, or by interrogating the database with active sessions, etc);

  • in the case when the token is not valid, your middleware will throw an error;

  • otherwise, it will either do nothing or it will inject some information (like the role of the user or an array of permissions, in the case of RBAC) into the request context by returning an object with that information from the auth.handler.

Say no more, let's implement a very basic authorization, that is going to check the Authorization header and will compare it with two pre-defined tokens:

I hope that it is really straightforward what we do in the above code: we react on two predefined tokens (it's really hard to call them tokens, to be honest 😬), based on which we will assign the permissions or in case if it's missing, we'll throw an error.

Next, we should somehow react to these permissions in our resolvers. Let's see how exactly we might do that:

This is what has changed comparing to the code from our Quick Start, we added a small if condition that checks the permissions in the request context argument:

Let's do the same for createPost.ts resolver:

That's it! We can now test how our changes impact the behaviour of the service.

Testing

Let's test this baby! First of all, quick go-through to see what we are actually expecting:

  1. query helloWorld should work as before, without any Authorization header (or with it);

  2. query allPosts should return an error if the header is not specified;

  3. query createPost should return an error if the header is not specified, or if the write permission does not equal true.

Let's test all these three scenarios.

1. query helloWorld should work as before

OK, so that worked as expected. Moving next.

For the sake of better readability, I will be formatting the JSON output of my curl commands with jq '.' command. That's why the way how JSON output looks like in the examples below may be different from what you see on your screen.

2. query allPosts

You shall not pass indeed! Let's try to add the correct token header to our request:

This time it worked as expected. Let's move on to the final part.

3. mutation createPost

Let's try without the token first (or with the user_token, the result should be the same:

Bang! πŸ’£Let's add the correct token now:

This time, it's a success.

Conclusion

In this short tutorial, we wanted to show you how to put in place a basic authorization system in your service. The idea was to show the capabilities of our SDK on a simple example, but the same approach could be applied to authorization with external systems like AWS Cognito or Auth0, as well as with the dedicated service on code.store.

Last updated

Was this helpful?