Securing a GraphQL API with Auth0 + GraphQL Shield

By

GraphQL has continued to increase in popularity since its public release by Facebook in 2015. Major companies like Netflix, PayPal, and Airbnb have enthusiastically adopted the REST-alternative and sung its praises. Here at UDig, we’ve seen it adopted by many of our clients, along with GraphQL Shield + AuthO, and have even included it as part of our upcoming training program for new hires.

One of the primary benefits of GraphQL is that clients have more granular control over the data they request. Instead of relying on backend developers to define the structure and content of API responses, frontend developers can build clients that query the API for just the fields that they need (and even retrieve multiple resources with a single request).

One challenge that comes with this granular control is making sure that users can only retrieve data they are authorized to see. Since there can be multiple routes in the graph to a single resource type, developers need to make sure there are no gaps that unauthorized users can gain access to.

This post aims to describe how to begin securing your GraphQL API with Auth0 and GraphQL Shield.

About this Tutorial | GraphQL Shield + AuthO

Technologies

Below are the key technologies that we will leverage to build our Auth0-protected GraphQL API:

  • Auth0
  • Node
  • Express
  • Postgres
  • Postgraphile
  • db-migrate
  • graphql-shield
Notes
  • This article assumes a basic understanding of GraphQL; to get some quick background on this technology, check out: https://graphql.org/
  • Although this is not a tutorial on Postgraphile, we will use it to help us quickly spin up a GraphQL API. I will explain some of the key elements that we’ll use to make our GraphQL API more user-friendly, but I will not go in depth on Postgraphile as a whole. For more information on Postgraphile, I recommend checking out their documentation: https://www.graphile.org/postgraphile/
  • All of the examples/instructions are written for Visual Studio Code, but you can use any IDE that you’d like _farmers-market-with-auth folder in the repo.
Requirements

For this exercise, we will build a GraphQL API that stores data about several fictional Farmer’s Markets located in Chicago neighborhoods. Our API will support two basic user types:

Customers (Public)

  • Any member of the public can view farmers market data, including the list of vendors, which locations they serve, and which products they sell.

Vendors

  • Vendors can view all data and edit their own data.
Pre-Requisites

Setting Up the Project

  1. The first thing you’ll need to do is clone the sample code to your local environment. Open up a terminal/command prompt, navigate to the directory where you want to store the project, and execute the following command:
    git clone https://github.com/pschmidtudig/farmers-market.git
  2. Next, open the project in your preferred editor (I’m using Visual Studio Code). Open the built-in terminal (ctl+` on Windows) and run the following command from the root folder to install all of the package dependencies defined in the package.json file:
    npm install
    
  3. npm instalInstall db-migrate globally by running the following command:
    npm install db-migrate -g

    If you haven’t already (see pre-requisites), install postgres on your local machine and create a database called market.

  4. Configure the database.sample.json file under the data folder. Update the “dev” entry with your postgres credentials and rename the file to database.json (this is the file that dbmigrate will look for).
  5. cd into the data folder and execute the following command to populate the project tables in your local database:
    db-migrate up
  6. Now you should be able to run a GraphQL server locally.cd back to the root folder and execute the following command to start the server:
    npm run start
  7. Navigate to the following url to interact with your GraphQL API: http://localhost:3000/graphiql
  8. Try running the following query in the query editor to get a list of all of the vendors, what locations they serve, and which products they sell:
    query {
  9. You should also be able to execute mutations to add and edit data in the database:
    mutation {

The Code

If you review the code base, you’ll notice that there is very little code or configuration required to get our project up and running. That’s because postgraphile is doing a ton of work for us behind the scenes. Let’s take a look at our server.js file:

//server.js

 

In this file, we’re initializing an instance of Express, then configuring it to use postgraphile (postgraphile as a library: https://www.graphile.org/postgraphile/usage-library/). Below is a description of the postgraphile arguments:

  • The first argument we pass in is the same database connection string that we used to apply our migrations. Postgraphile will use this connection to generate the graphql schema.
  • The second argument is the name of the schema that postgraphile should target in our database (we are using the default public schema)
  • The third argument is the postgraphileOptions object, which is where we customize the behavior of our postgraphile instance. I’ve added some comments to describe each of the configured options.

A key component of the postgraphileOptions object is the appendPlugins property. Later in this article, we’ll add a new custom plugin to this array to further customize the behavior of our GraphQL API.

Add Authentication/Role-Based Authorization

Our goal is to protect our GraphQL API by requiring users who want to edit data to be authenticated with Auth0 and authorized with the proper permissions. To do this, we first need to configure Auth0 for use in our application.

Configure Auth0

The following steps describe how to configure your Auth0 environment to support the GraphQL API backend. If you haven’t already, you can setup a free Auth0 account to use for this tutorial.

Create a Tenant

  1. The first thing we’ll want to do after logging into our Auth0 dashboard is setup a custom tenant. At the top left of your screen, click the menu dropdown and then the Create Tenant option at the bottom of the list.
    create tenant
  2. Give your tenant a unique domain name and leave the other options as default. Click Create.
    • Note: Make a note of your domain name, as you will need it when configuring Auth0 in the postgraphile project

    configure tenant

Create an API

Next, we need to create an API application in Auth0 that we’ll interface with from our postgraphile project.

  1. Navigate to the Application -> APIs page.
    create an api
  2. Create and configure a new API project with the following configuration:
    • Name: Farmer’s Market GraphQL API
    • Identifier: farmers-market-api
    • Signing Algorithm: RS256

    configure your api

  3. Auth0 should redirect you to the newly created API page. Navigate to the settings tab and scroll down to RBAC settings. Make sure that the Enable RBAC and Add Permissions in the Access Token are both toggled on and Save your changes.
    rbac settings
  4. Click on the permissions tab and add a new permission called write:all
    auth permissions

Configure a Test User

Next, we need to create a test (vendor) user with the proper privileges for editing data in our GraphQL API.

  1. From the Auth0 menu to the left, navigate to the User Management page. Click the blue button on the right to create a new user.create user
  2. For this first user, set the email to giussepe@farmersmarket.com. Make sure Username-Password-Authentication is selected in the Connection dropdown.
    create guissepe
  3. Open the detail page for your new user and navigate to the Permissions tab. Click on the Assign Permissions button.
  4. Select our Farmer’s Market GraphQL API from the dropdown at the top, and check the box for the write:all permission we created in a previous step. Click Add Permissions.
    configure permissions

Update GraphQL API Project to Support Authorization

Now that we have our Auth0 environment configured, we can update our GraphQL API project to support authorized users.

Add an .env file

Create a .env file in the root folder of your project to store the Auth0 configuration. Add two entries named AUTH0_AUDIENCE and AUTH0_DOMAIN and configure them as follows:

AUTH0_AUDIENCE=farmers-market-api

 

Add Dependencies

Run the following commands from your root folder to install the packages that we’ll use for validating JWT tokens from Auth0:

npm install --save jwks-rsa

 

Update server.js to process jwt tokens

Add references to both of the installed packages at the top of your server.js file:

// server.js

 

Add the following code between where you initialize express (const app = express();) and the postgraphile declaration (app.use( postgraphile( … )).

// server.js

 

With these changes, Postgraphile will check for a Bearer token in the Authorization header before continuing to process a request. Since we allow all data in our GraphQL API to be read by the public, we set the credentialsRequired flag to false.

Update server.js to Retrieve User Permissions from Access Token

Once we confirm that a user’s authorization token is valid, we need to make sure that the user’s token information is passed into the GraphQL context. This will allow us to examine a user’s claims and make decisions about what data they can see based on those claims.

To do this, we’ll add an additional option in our postgraphile configuration called

additionalGraphQLContextFromRequest.

Each time a request is executed against the API, this code will intercept the request object, check to see if there is an auth property on the request (added from our jwt validation above), and if so, assign the permissions from Auth0 to a user object. By doing so, we’re making user available as part of the Express server context.

// server.js

 

Note: If you want to test your code at this point, jump down to Testing with an Authenticated/Authorized User for instructions on retrieving an access token from Auth0.

Configure GraphQL Shield

In order to make use of our newly available user permission data, we need to configure GraphQL Shield.

Create Rules and Permissions

GraphQL Shield is made up of two main components:

  • Rules: Rules are essentially functions that can review the context of an incoming request and return true or false depending on whether the request meets a set of criteria. Rules are typically named to reflect a user’s authorization status, such as “isAuthorized” or “canReadProductData”
  • Permissions: Permissions describe how/where rules are applied to the GraphQL schema to determine what a user has access to, based on their authorization.
Add dependencies

Run the following command from your root folder to install the graphql-shield and @graphql-tools/wrap packages:

npm install --save graphql-shield
Setup the Folder Structure

Create a new folder in your src folder called auth. Add the following files:

  • rules.js
  • permissions.js
Create GraphQL Shield Rules

The first thing that we need to do is create some rules around who can access the data in our API. We’ll first create a rule called isAuthenticated that simply checks that a user is calling the API with a valid token from Auth0.

The second rule, canEditData will perform an additional check to see if a user has the write:all permission.

// rules.js
Apply Rules to the GraphQL Schema

Now that we have a set of rules, we need to add them to the appropriate GraphQL endpoints or objects that we want to protect.

First, we’ll update the Vendor object so that only authenticated users can get a vendor’s contact information (email). Next, we’ll lock down the entire Mutation object, so only authenticated users with edit permissions can execute mutations on our API.

For illustration purposes, I also added an option called fallbackRule to our permissions. We’ll set it to true here, which is the default value, but if you set this value to false, any object/field in the API without an explicit allow rule will have a deny rule by default.

// permissions.js

Create GraphQL Shield Plugin

Because we’re using postgraphile to setup our API, we need to perform an additional step to apply the graphql-shield middleware to our API.

Create the plugin

Create a new subfolder in your src directory called plugins. In it, add a new file called graphQLShieldPlugin.js. Add the following code:

// graphQLShieldPlugin.js

 

The key line in the code above is return applyMiddleware(wrappedSchema, permissions.permissions). This takes our generated graphQL schema as an input and applies our graphql shield permissions.

I’ve added the wrapSchema step as a workaround for an error that occurs when using postgraphile with graphql-shield, described here: https://github.com/maticzav/graphql-middleware/issues/369

For more information on schema wrapping with @graphql-tools/wrap, see https://www.graphql-tools.com/docs/schema-wrapping

Add the plugin to postgraphile

Finally, we need to update our server.js code to include our new plugin.

Add a reference to the plugin file at the top of server.js:

const graphQLShieldPlugin = require(‘./plugins/graphQLShieldPlugin’);

 

Then add graphQLShieldPlugin to appendPlugins on the list of postgraphile options:

app.use(

Testing with an Unauthenticated User

Restart the server with the latest updates (npm run start). With the changes we’ve applied to our project, we should still be able to execute the following query in GraphiQL (http://localhost:3000/graphiql):

Request:

query {

 

Result:

GraphQL shield successful query

However, if you add email to the request, you should get an Authorization error, since we require a user to be authenticated to query that field:

Request:

query {

 

Result:

GraphQL shield not authorized error

You should see a similar error when attempting to execute a mutation.

Testing with an Authenticated / Authorized User

In order to verify that our GraphQL Shield rules are operating as expected, we need to add a valid Authorization header to our GraphiQL call. For that, we need to get an authorization token from Auth0.

The easiest way to retrieve an authorization token from Auth0 is by using their Auth0 Authentication API Debugger extension through the Auth0 dashboard. However, to use the debugger, we first we need to configure an Application to represent the client that we’re logging in from.

Configure the Application (Client)
  1. Navigate to the Auth0 dashboard and and then to the Applications -> Applications page. For our purposes, you can just update the Default App that is created with your tenant; click on the application link to view its details.
    • Note: Do not use the application that was auto-generated with your API, ex. Farmer’s Market GraphQL API (Test Application). This is configured as a Machine to Machine application and cannot be used to login with user credentials.
  2. Scroll down to the input box for Allowed Callback Urls and paste the following url, replacing {your_auth0_domain} with your own domain: https://{your_auth0_domain}.us.webtask.run/auth0-authentication-api-debugger.
  3. Scroll to the bottom of the page and save your changes.
Install the Auth0 Authentication API Debugger Extension
  1. From the Auth0 dashboard, navigate to the Extensions page and search for Auth0 Authentication API Debugger. Click on the application that comes up and click install. Once the installation completes, click on the extension to open it (Auth0 will prompt you to authorize the app; click Accept).
  2. When the extension opens, you should be on the Configuration tab. The only thing you need to update on this page is the Application dropdown (set it to Default App, or whichever Application you configured with the callback url)
  3. Navigate to the OAuth2/OIDC page and scroll down to the Audience property. Set this to your API identifier (farmers-market-api) and switch the Use Audience toggle to true.GraphQL Shield + AuthO
  4. Scroll back to the top of the page and click the first button under user flows labelled OAuth2/OIDC Login.
    login GraphQL Shield + AuthO
  5. Auth0 should prompt you to enter login credentials. Use the user credentials for Giussepe (giussepe@farmersmarket.com) that you configured earlier.
  6. If the login is successful, you should see a response that includes an access_token.
  7. Copy the value for “access_token”
    GraphQL Shield + AuthO DebuggerNote: If you scroll down to the response under Access Token, you can review the token details and ensure that the expected permissions are included in the token.
Add the Access Token to your GraphiQL Request

Finally, all we need to do to access our GraphQL mutations is add the valid user access token to our request headers.

Navigate to GraphiQL and open up the Request Headers tab in the window below the query editor. Add an Authorization header with a value of “Bearer {access_token}”:

Mutation Success GraphQL Shield + AuthO

If the token you provided is valid, you should now be able to execute the following query:

query {

 

Since our user has the write:all permission, you should also be able to execute mutations:

mutation {

 

As an additional test, try creating an additional user in Auth0 without adding any permissions. The new user should be able to query the email property on a Vendor, but should get an Unauthorized error if they try to execute a mutation!

Closing Notes | GraphQL Shield + AuthO

If you run into any issues or want to compare your code against the expected state, take a look at the _farmers-market-with-auth folder in the repo, which contains the completed code from this tutorial.

You should now have a basic GraphQL API setup using postgraphile that can be secured with Auth0 and GraphQL Shield rules. Now that you have the basic configuration in place, here are some additional things to try:

  • Create more granular permissions in Auth0 to allow for more specific rules in GraphQL shield.
  • Leverage Roles in Auth0 to apply a set of permissions to multiple users.
  • Create some Auth0 Flows to customize your login process. For example, add additional user context (like their Auth0 role) to their access token.

One thing you may notice is that, in the current state of our project, any user with write privileges can edit any data in our API. This is obviously not ideal, as we mentioned before that vendors should only have access to edit their own data. Look out for a future blog where we’ll address this problem using row level security in Postgres!

About The Author

Page is a Principal Consultant on the Software team.