DEV Community

Cover image for Self Hosted Hasura + GoTrue = ❤️
Mike CK
Mike CK

Posted on • Originally published at mikeck.elevatika.com

Self Hosted Hasura + GoTrue = ❤️

Hasura + GoTrue = ❤️

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!

Background

Setting up Hasura 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 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 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:

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:
Enter fullscreen mode Exit fullscreen mode

The Caddy server Caddyfile looks like following:

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
}

Enter fullscreen mode Exit fullscreen mode

_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:

docker-compose exec gotrue /bin/ash
gotrue migrate
Enter fullscreen mode Exit fullscreen mode

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:

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

JavaScript:

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));
Enter fullscreen mode Exit fullscreen mode

Get a Token for This User

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'
Enter fullscreen mode Exit fullscreen mode
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));
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    "token_type": "bearer",
    "expires_in": 864000,
    "refresh_token": "aWF0IjoxNTE2MjM5-2kNw"
}
Enter fullscreen mode Exit fullscreen mode

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:

--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
Enter fullscreen mode Exit fullscreen mode

🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀

https://github.com/CkCreative/hasura_gotrue

Top comments (2)

Collapse
 
robinmobin profile image
RobinGlub

Starting with Hasura v1.3.3 you can actually specify a claims-map config for this use-case.

"claims_map": {
  "x-hasura-allowed-roles": {
    "default": ["user", "vendor"]
  },
  "x-hasura-default-role": {
    "default":"user"
  },
  "x-hasura-user-id": {
     "path":"$.email"
  }
}
Enter fullscreen mode Exit fullscreen mode

Would using this config without modifying GoTrue have the same effect?

Collapse
 
mikeck profile image
Mike CK

Aha... This is very neat. I had not looked at this part of the documentation. Thanks a lot!