Self Hosted Hasura + GoTrue = ❤️
Tags: hasura, gotrue, jwt, authentication
Published: 2021-02-20 04:07:54

---
title: Self Hosted Hasura + GoTrue = ❤️
published: true
description: This is a slightly extended version of Netlify's GoTrue. It includes a `docker-compose.yaml` file to deploy it together with Hasura. This set up uses two databases, PostgreSQL for Hasura and MariaDB for GoTrue. Have fun!
tags: Hasura, GoTrue, JWT, Authentication
cover_image: https://mikeck.elevatika.com/images/spacex.jpg
canonical_url: https://mikeck.elevatika.com/posts/hasura-and-gotrue-authentication-authorization-jwt-docker-compose/
---
## Hasura + GoTrue = ❤️

This is a slightly extended version of [Netlify's GoTrue](https://github.com/netlify/gotrue). It includes a `docker-compose.yaml` file to deploy it together with [Hasura](https://hasura.io/docs/1.0/graphql/core/index.html). This set up uses two databases, [PostgreSQL](https://www.postgresql.org/) for [Hasura](https://hasura.io/docs/1.0/graphql/core/index.html) and [MariaDB](https://mariadb.org/) for [GoTrue](https://github.com/netlify/gotrue). Have fun!

## Background

Setting up [Hasura](https://hasura.io/docs/1.0/graphql/core/index.html) is very easy. However, you immediately get disappointed that you don't get authentication out of the box. You have to read through a lot of tutorial blogs just to end up using paid options or Firebase.

[GoTrue](https://github.com/netlify/gotrue) is a simple yet solid authentication and user management tool. It is very light and straightforward. You can sign up users, verify them and also help them reset their passwords. The means of authentication is JWT, meaning that after signing up, a client sends a username and password to the system and they get an `access_token` and a `refresh_token`. This way, the client can be able to attach the token each time it makes a request.

[Hasura](https://hasura.io/docs/1.0/graphql/core/index.html) is a very neat GraphQL engine built on top of PostgreSQL using Elixir. This gives you highly scalable GraphQL APIs that you can consume using web, mobile and desktop applications written in various programming languages. Hasura is nice in that it gives you room for multiple authentication and authorization options including JWT and WebHooks.

When you send a JWT to hasura in form of `Authorization: Bearer token`, Hasura engine checks the signature of the token using a preconfigured secret. Therefore, it doesn't have to send the token to the issuer from the server side to confirm its authenticity, Hasura simply checks the signature and, if it checks out, proceeds to authorize the request according to the claims available inside the token.

Which brings us to the claims and GoTrue. Hasura requires some custom claims which are not available by default inside the tokens generated by GoTrue. So, I simply took GoTrue source code and extended the `token.go` file to include Hasura claims inside the generated token.

This means the rest of GoTrue code works as expected and will be easy to update in future. The added code is less than 10 lines, and I am new to Go, so perhaps there is an even shorter way of adding the feature.

I also included the deployment file I use, this should work out of the box, but there is a catch.

## Deployment

Clone this repositoy, modify the `.env` file to include your preferred details for passwords, domain and smtp settings for emails then run `docker-compose up -d`.

The `docker-compose.yaml` file looks like this:

```yaml
version: "3"

services:
  postgres:
    image: postgres:12
    restart: always
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: pgpassword

  hasura:
    image: hasura/graphql-engine:v1.3.3
    ports:
      - "5000:5000"
    depends_on:
      - "postgres"
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:pgpassword@postgres:5432/postgres
      ## enable the console served by server
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
      ## enable debugging mode. It is recommended to disable this in production
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_UNAUTHORIZED_ROLE: public
      HASURA_GRAPHQL_SERVER_PORT: 5000
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      ## uncomment next line to set an admin secret
      HASURA_GRAPHQL_ADMIN_SECRET: AdminSecretHere
      HASURA_GRAPHQL_JWT_SECRET: '{"type": "HS256", "key": "changethismorethan32characterstring"}'

  caddy:
    image: abiosoft/caddy
    depends_on:
      - "web"
    restart: always
    environment:
      ACME_AGREE: "true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/Caddyfile
      - caddy_certs:/root/.caddy

  db:
    image: mariadb:10
    restart: unless-stopped
    env_file: .env
    environment: 
      MYSQL_ROOT_PASSWORD: dbpassword
    volumes:
      - mariadb:/var/lib/mysql
    ports:
      - 3306
  gotrue:
    build: gotrue
    restart: unless-stopped
    env_file: .env
    environment:
      - PORT=${GOTRUE_PORT}
      - "DATABASE_URL=${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(db:3306)/${MYSQL_DATABASE}?parseTime=true&multiStatements=true"
    ports:
      - ${GOTRUE_PORT}:${GOTRUE_PORT}
    depends_on:
      - db

volumes:
  caddy_certs:
  db_data:
  mariadb:
```

The Caddy server `Caddyfile` looks like following:

```Caddyfile
hasura.example.com {
  bind {$ADDRESS}
  proxy / hasura:5000 {
    transparent
  }
  tls email@example.com
}

gotrue.example.com {
  bind {$ADDRESS}
  proxy / gotrue:9999 {
    transparent
  }
  tls email@example.com
}

```

_Modify the `docker-compose.yaml` file and the `Caddyfile` to suite your set up.

You will then need to manually run migrations inside the running `gotrue` container by doing:

```zsh
docker-compose exec gotrue /bin/ash
gotrue migrate
```

The first command brings up the shell of the `gotrue` container that is running. The `gotrue` image is built from alpine, therefore the default shell is `ash`, don't bother looking for `zsh` or `bash`.

The second command is what you will run inside the container, this will run the migrations and you are now ready to test.

## Testing

### Sign Up a User

Curl:

```zsh
curl --location --request POST 'https://gotrue.example.com/signup' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "user@email.com",
  "password": "password"
}'
```

JavaScript:

```js
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");

var raw = JSON.stringify({"email":"email@example.com","password":"password"});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://gotrue.example.com/signup", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

### Get a Token for This User

```zsh
curl --location --request POST 'https://gotrue.example.com/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=email@example.com' \
--data-urlencode 'password=password'
```

```js
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

var urlencoded = new URLSearchParams();
urlencoded.append("grant_type", "password");
urlencoded.append("username", "email@example.com");
urlencoded.append("password", "password");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

fetch("https://gotrue.example.com/token", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

Response:

```json
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    "token_type": "bearer",
    "expires_in": 864000,
    "refresh_token": "aWF0IjoxNTE2MjM5-2kNw"
}
```

### Using the Token in graphQL requests to Hasura

Given the token above, when making a request to Hasura, add the access token provided for example:

```zsh
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
```

🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀

[https://github.com/CkCreative/hasura_gotrue](https://github.com/CkCreative/hasura_gotrue)