Build Secure GraphQL API and Deploy to AWS

January 26, 2019

Building a secure APIs is the main job as a backend developer. But how does it works when using GraphQL? And how to deploy it to AWS Lambda? Well, there are several approaches that we will dive into in this course. Will will learn how to build a simple API with GraphQL. Then, we will implement Authorization to our API, and deploy it to AWS Lambda with Serverless Platform.

Prerequisites

  • A bit knowledge of Javascript & NodeJS
  • Serverless CLI Installed on your machine
  • An AWS account

Getting started

I have prepared the boilerplate of this project. You can clone it, or you can follow along this tutorial step by step if you want.

$ git clone https://github.com/reshuttle/graphql-lambda-demo.git

$ npm install

Clone the repository on github, and go into that folder. Run npm install. Then you will see something like this.

$ tree
├── handler.js
├── middlewares.js
├── package.json
├── serverless.yml
└── yarn.lock

Setting up our GraphQL Server

In this tutorial, we're going to use the GraphQL Yoga, which is the simplest way to build GraphQL Server with NodeJS. Let's write our first code then.

handler.js
const { GraphQLServerLambda } = require("graphql-yoga")

const typeDefs = `
  type Query {
    hello: String! 
  }
`

const resolvers = {
	Query: {
		hello: () => "Hello world!",
	},
}

const lambda = new GraphQLServerLambda({
	typeDefs,
	resolvers,
})

exports.server = lambda.graphqlHandler
exports.playground = lambda.playgroundHandler

Now, we have a GraphQL Schema that has a hello query which just returns a Hello World string. Then we've exported our lambda and playground handler which the AWS Lambda will looking for.

Setting up Serverless

Serverless is a toolkit to develop a serverless functions, which we can use in various web services Google Cloud Platform, Microsoft Azure, and Amazon Web Services (AWS).

Okay, so let's configure the serverless setup by adding serverless.yml.

serverless.yml
service: graphql-lambda-demo

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs10.15

functions:
  graphql:
    handler: handler.server
    events:
      - http:
          path: /
          method: post
          cors: true
  playground:
    handler: handler.playground
    events:
      - http:
          path: /
          method: get
          cors: true

In this setup, we have defined two functions. First is the graphql handler, which is the GraphQL API. Second is the playground handler which is a GraphQL IDE to to Explore our GraphQL API.

Then, we are now using the serverless-offline plugin. It is a great plugin to run our functions locally in our machine for development.

You can run the serverless-offline by running yarn start in the command line. By default, it will started at http://localhost:3000. Then, you will see something like this.

graphql-playground

Deployment

Now, it's time to deploy our API to AWS Lambda. Make sure that you already have and AWS Account, and AWS CLI installed. Then, run this command below in your command line. This will deploy our functions to AWS Lambda.

$ serverless deploy -v

Then, you could test if the function is working by invoking the GraphQL API.

$ serverless invoke -f graphql -l

After the deployment is done, login to your AWS Console.

aws console

Then, click the Lambda Service on the AWS Services. You will see something like this.

aws console lambda

Now, you have two functions deployed. Great job! Let's move on to the more advanced topics.

Authentication

There are two different authentication approach that we're going to take a look at in this course. There are session based approach and token based approach. In this case, we're going to use the token based authentication and I will explain it later. But for now, let's compare it side by side.

Session based authentication

In session based authentication, the server will create a session for the currently logged in user. The session id stored in the browser cookie. The cookie will sent to the server on every request, Since the user stays logged in. Then, the server compare the session id stored in the cookie which sent by the browser against the session that stored on the server to verify the user's identity.

Token based authentication

When the user log in to the server, the server will respond to the client with a token, which is an encoded data that contains user id as the payload. The browser store the token (usually in local storage), and includes the token in the header with every request. Then, the server will verify the token and extract the payload inside the token to get the user identity.

Many web apps use JSON Web Token (JWT) instead of sessions for authentication. In that way, the user state is stored inside the token, instead of the server. Most of the modern apps use this approach, because of the scalability and mobile device authentication.

comparison

Setting up users

Before we get started, we need to set up a few mock users that we will use to perform authentication. Let's add few things to our handler.js.

handler.js
// ...

const jwt = require("jsonwebtoken")

// ...

const users = [
	{ id: 1, name: "John Doe", email: "johndoe@gmail.com" },
	{ id: 2, name: "Jane Doe", email: "janedoe@gmail.com" },
]

const typeDefs = `
  type Query {
    login(email: String!): String!
    secret: String!
  }
`

const resolvers = {
	Query: {
		login: (_, { email }) => {
			const user = users.find((user) => user.email === email)
			if (!user) throw new Error("User not found!")
			const token = jwt.sign({ id: user.id }, "your secret key")
			return token
		},
		secret: () => "Secret data",
	},
}

// ...

In this case, the user data doesn't stored to any database and we don't use password to login yet. To build the production API, you need to consider using bcrypt for passsword hashing.

Now we have the mock users data, and there are secret field in our query which just return a string. But, we want to secure this field so only the authenticated users can query this field. Then, we have the login query which return a token. The token contains the id of the user which we will use it later. So let's jump to the next section.

GraphQL Middleware

If you have already familiar with frameworks like Express, Koa, and Hapi, you must have heard about middleware. Basically, it's a function which will be triggered everytime the client request to the server. GraphQL Yoga has it own implementation of the middleware function.

GraphQL Shield

Another third party library which we will use is GraphQL Shield. It's a cool library that helps us to create permissions layer in our GraphQL API. Let's use it in middlewares.js file.

middlewares.js
const { rule, shield } = require("graphql-shield")

const isAuthenticated = rule()(async (parent, args, ctx, info) => {
	return Boolean(ctx.userId)
})

const permissions = shield({
	Query: { secret: isAuthenticated },
})

module.exports = [permissions]

In this case, we check the user state (authenticated or not) by checking the ctx.user. But what does it mean? GraphQL has a feature called resolver context, which is a set of data that could be retrieved across resolvers. But where is the context data came from? Let's jump to the next section.

GraphQL Context

To get the current user state, let's jump to the handler.js and add few things.

handler.js
// ...

const jwt = require("jsonwebtoken")
const middlewares = require("./middlewares")

// ...

const lambda = new GraphQLServerLambda({
	typeDefs,
	resolvers,
	middlewares: middlewares,
	context: (req) => {
		const token = req.event.headers.authorization
		if (token) {
			const { id } = jwt.verify(token, "your secret key")
			return { ...req, userId: id }
		} else {
			return { ...req }
		}
	},
})

// ...

Now, we're using the middlewares that we require from middleware.js. In the context function, First, we check if the client pass the token through the request headers. If the user pass the token, we extract the token to get the id of the user. Finally, we return the userId which we use in our middleware function.

Testing out our server

Let's test if our server works.

If you access secret without token. The server will throw an error:

not authorised

You can get the token by logging in with the email:

get token

You can access the secret field if you insert the token to the headers:

accessing secret

Conclusion

GraphQL is the one of most hottest topics in 2019. And understanding the core concept is a little bit challenging for most backend developers who's already comfortable with REST APIs. Also deploying a GraphQL API is tough. But serverless platform make your life easier.

Profile picture

Abdurrahman Fadhil

I'm a software engineer specialized in iOS and full-stack web development. If you have a project in mind, feel free to contact me and start the conversation.