Building a secure GraphQL app in minutes with Hasura
Tags: graphql, react, javascript, security
Published: 2020-07-27 15:55:00

Hasura is an amazing service which you can point at any PostgreSQL database to automatically spin up a realtime GraphQL API for your data. In this guide, we'll use Hasura to build a secure todo list app. Here's a quick preview of the app we'll build ✅ 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8cx5xa4bwrvp7tp20b9h.png)


# 1. Launch the backend

Head over to [Hasura](https://hasura.io) and create an account. When Hasura asks to connect your database, choose **"Try a free database with Heroku"**.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ptyirejav0a6zfgzmku2.png)

After following the one-click setup with Heroku, you'll have a new instance and database running at a URL similar to `https://<YOUR_HEROKU_PROJECT>.herokuapp.com`. Navigate to the **Projects** tab and click **Launch Console** to open up your app's Hasura dashboard. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m7v3tacosp49yju94qod.png)

# 2. Data tables

Navigate to the **Data** tab and click **Create Table**. Let's name the table `todos` add a couple columns such as: 
- `id :: Integer (auto-incremented)`
- `title :: Text`
- `is_completed :: Boolean`
- `user_id :: Text`
- `created_at :: Timestamp`

Remember to mark the `id` column as unique and set it as the primary key!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mcja34ybmp0sej7iabiv.png)


# 3. Authentication

One thing you need to know about Hasura is that it delegates authentication. This means you can use any 3rd-party auth provider you want. For this guide, we'll use Feather which is a lightweight API for easily adding authentication and user accounts to any app. 

In order to get Feather to work with Hasura, we need to set up a special environment variable called `HASURA_GRAPHQL_JWT_SECRET`. This will tell Hasura how to validate user ID tokens issued by Feather (they're really [JWTs](https://en.wikipedia.org/wiki/JSON_Web_Token) under the hood). So head to over [Feather](https://feather.id) and register a new project. After creating the project, navigate to the **Settings** tab on your Feather dashboard and copy the project ID. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pzb53f3f9scbs0kuazx6.png)

Plug your Feather project ID into the `"audience"` field of the JSON template below.

```js
{
  "type":"RS256",
  "jwk_url": "https://api.feather.id/v1/.well-known/jwks",
  "issuer": "api.feather.id",
  "audience": "<YOUR_PROJECT_ID>"
}
```

Now to create the environment variable, head to your project's Heroku dashboard at `https://dashboard.heroku.com/apps/<YOUR_HEROKU_APP>`, navigate to the **Settings** tab, and find the section titled **Config Vars**. Create a new value called `HASURA_GRAPHQL_JWT_SECRET` and paste in the JSON value.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6cqlatmrf4pbrlmon8mw.png)

Alright, we've setup authentication! Let's continue by hooking up Feather with Hasura's authorization system.


# 4. Authorization

Another one of Hasura's features is that it provides fine-grained row-level authorization controls straight out of the box! 🤯 To set it up, navigate to the **ID Tokens** tab on your project's Feather dashboard. Copy the JSON struct below and paste it into the text box titled **Custom Claims**. Click the **Save** button to commit the changes. 

```js
{
  "https://hasura.io/jwt/claims": {
    "x-hasura-user-id": "{{.USER.ID}}",
    "x-hasura-allowed-roles": ["user"],
    "x-hasura-default-role": "user"
  }
}
```

From now on, every user who signs in to the app will be issued a `"user"` role when initiating requests to Hasura. This will allow us to setup data access rules on the `"user"` role to ensure users can only create, access, and modify their own todos. 

So head back to the **Data** tab on your Hasura dashboard and navigate to the **Permissions** sub-tab on the todos table. Add a new role called `"user"` and click on the **insert** operation to edit it. Let's start by adding a column preset on the `"user_id"` column to set it to `"X-Hasura-User-Id"`. This means whenever someone creates a new todo, Hasura will automatically set the user ID on the new row. Cool, right!? 😎

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t8qvhsz5qndn2fw62i1i.png)

Let's wrap up authorization by adding custom checks on the **select**, **update**, and **delete** operations. We'll only authorize one of these operations if the caller's `"X-Hasura-User-Id"` matches the `"user_id"` column of the rows being operated on.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/16ulcjkn9yy6cx3chk57.png)


You just setup an entire backend complete with a PostgreSQL database, a GraphQL API, user authentication, and row-level authorization without writing a single line of code! Let's finish up with the fun part of every app: the frontend! 🥳


# 5. Frontend

Open your Terminal, find a clean directory, and run the following commands to scaffold a new React application with all the dependencies we'll need:

```bash
$ npx create-react-app hasurademo && cd hasurademo && yarn add @apollo/client apollo-link-context apollo-link-http apollo-cache-inmemory feather-client-react graphql graphql-tag
```

Now open up the project in your favorite text editor and create a new file called `src/feather.js`. Copy the publishable API key from your project's Feather dashboard and use it to initialize a Feather client.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qz9rycapkf19bm7u6rxy.png)

```js
import {FeatherClient} from "feather-client-react"

export const feather = FeatherClient("pk_live_...")
```


Now we're all setup to talk with the Feather API, let's create a GraphQL client to send requests to our Hasura API. For this, we'll use [Apollo](https://www.apollo.io). Create a new file called `src/apollo.js` and add the following code:

```js
import { ApolloClient } from "@apollo/client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import { setContext } from "apollo-link-context";
import { feather } from "./feather";

const httpLink = new HttpLink({
  uri: "https://hasura-test-pliao.herokuapp.com/v1/graphql",
  fetchPolicy: "network-only"
});

const authLink = setContext((_, { headers }) =>
  feather
    .currentUser()
    .then(u => ({
      headers: {
        ...headers,
        authorization: `Bearer ${u.tokens.idToken}`
      }
    }))
    .catch(_ => ({ headers }))
);

export const apollo = new ApolloClient({
  cache: new InMemoryCache(),
  link: authLink.concat(httpLink)
});
``` 

Now let's hook up these clients to the React component tree so the app can use them. Open `src/index.js` and add the following code:

```js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { FeatherProvider } from "feather-client-react";
import { feather } from "./feather";
import { ApolloProvider } from "@apollo/client";
import { apollo } from "./apollo";

ReactDOM.render(
  <React.StrictMode>
    <FeatherProvider client={feather}>
      <ApolloProvider client={apollo}>
        <App />
      </ApolloProvider>
    </FeatherProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```

Now that all the communication pieces are ready, let's flush out the visual components. Open `src/App.js`. The first thing we'll do is check Feather to see if a current user is signed in. If not, we'll show the authentication form. Otherwise, let's list the user's todos.

```js
import React from "react";
import { AuthenticationForm, useCurrentUser } from "feather-client-react";
import Todos from "./Todos";

const styles = {
  title: provided => ({
    ...provided,
    fontSize: "40px",
    fontWeight: 700
  })
};

function App(props) {
  const { loading, currentUser } = useCurrentUser();

  if (loading) return <div />;
  if (!currentUser)
    return (
      <div className="app">
        <AuthenticationForm styles={styles} />
      </div>
    );
  return (
    <div className="app">
      <div className="app-header">
        <h1>My to-do list</h1>
        <p>{currentUser.email}</p>
      </div>
      <Todos />
    </div>
  );
}

export default App;
```

Notice Feather comes with a pre-built authentication form that can be given a custom style. This saves a bunch of time when setting up new projects because it even handles things like password resets without any additional work! ⚡️

Now let's add a way for the user to view their todos. Create a new file called `src/Todos.js` and add the following code:

```js
import React from "react";
import Todo from "./Todo";
import NewTodo from "./NewTodo";
import { useQuery, gql } from "@apollo/client";

export const GET_TODOS = gql`
  query GetTodos {
    todos {
      id
      title
      is_completed
    }
  }
`;

function Todos(props) {
  const { loading, error, data } = useQuery(GET_TODOS);

  if (error) return <p>{error.message}</p>;
  if (loading) return <p>Loading ...</p>;
  return (
    <div>
      {data.todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
      <NewTodo />
    </div>
  );
}

export default Todos;
```

Notice how we can use Apollo to just send GraphQL requests directly from React! Next we need a way for users to edit their todos. Create a new file called `src/Todo.js` and add the following code: 

```js
import React from "react";
import { useMutation, gql } from "@apollo/client";

const TOGGLE_TODO = gql`
  mutation ToggleTodo($id: Int!, $is_completed: Boolean!) {
    update_todos(
      where: { id: { _eq: $id } }
      _set: { is_completed: $is_completed }
    ) {
      returning {
        id
        is_completed
      }
    }
  }
`;

export default function Todo(props) {
  const [toggleTodo] = useMutation(TOGGLE_TODO);

  const onChange = e => {
    toggleTodo({
      variables: {
        id: props.todo.id,
        is_completed: !props.todo.is_completed
      }
    });
  };

  return (
    <div style={{ display: "flex", flexDirection: "row" }}>
      <input
        type="checkbox"
        className="todo-checkbox"
        name={props.todo.id}
        checked={props.todo.is_completed}
        onChange={onChange}
      />
      <p>{props.todo.title}</p>
    </div>
  );
}
``` 

Finally this todo app wouldn't be very useful if users can't create todos! Create a new file called `src/NewTodo.js` and add the following code:

```js
import React, { useState } from "react";
import { useMutation, gql } from "@apollo/client";

const CREATE_TODO = gql`
  mutation CreateTodo($title: String!) {
    insert_todos_one(object: { title: $title }) {
      id
      title
      is_completed
    }
  }
`;

function NewTodo(props) {
  const [title, setTitle] = useState("");
  const [createTodo] = useMutation(CREATE_TODO);

  const onSubmit = e => {
    e.preventDefault();
    createTodo({ variables: { title } });
  };

  const onChange = e => {
    setTitle(e.target.value);
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        className="new-todo-input"
        value={title}
        onChange={onChange}
        type="text"
        placeholder="Today I will..."
      />
    </form>
  );
}

export default NewTodo;
```

Last (but not least!), let's give the app a little styling to make it look nice. Open up `src/index.css` and add the following CSS classes 🎨:

```css
.app {
  padding: 80px;
  max-width: 400px;
  margin: 20px auto;
}

.app-header {
  margin-bottom: 40px;
}

.todo-checkbox {
  margin: auto 10px auto 0px;
}

.new-todo-input {
  font-size: 20px;
  padding: 20px;
  width: 100%;
  margin-top: 40px;
}
```

# Wrapping up

Phew! That was a lot of code! But if you've followed along, you can head back to Terminal and run `yarn start` to run the app locally. 

Let's review everything we did:

1. Deployed a PostgreSQL database instance to Heroku.
2. Used Hasura to generate a GraphQL API over that database.
3. Set up Feather to provide authentication and issue user roles. 
4. Added authorization rules to ensure users can only access their own data.
5. Created a frontend React app using Feather and Apollo.

You can checkout the complete codebase for this app [on Github](https://github.com/nickgarfield/hasurademo). For in-depth guides to each of the technologies used in this app, you can checkout their individual documentation at:
- [Hasura docs](https://hasura.io/docs/1.0/graphql/manual/index.html)
- [Feather docs](https://feather.id/docs)
- [Apollo docs](https://www.apollographql.com/docs/)