NestJS Hasura Integration via Schema Stitching And JWT Auth

Source code can be found here: https://github.com/artonio/nest-jwt-hasura

The following article provides the code and explanation which demonstrates the following functionality:

  1. Hasura integration with NestJS via GraphQL Schema Stitching
  2. Hasura JWT Authentication and passing JWT to Hasura
  3. TypeORM to design and seed Postgres DB used by Hasura
  4. Admin role and Observer role which have different permission on different GraphQL Fields
  5. Login with different users who have different roles with different permissions

This article assumes that you know what Hasura is, if not I recommend the following YouTube video.

Hasura seems like a magic solution that promises instant backend which provides GraphQL Resolvers out of the box over a PostgreSQL Database. In Hasura you are able to design your relational database, do CRUD on the data in said DB and generate resolvers (Queries, Mutations and Subscriptions) based on your database schema. It also provides some really useful features like permissions out of the box for different user roles. Different users with different roles have different abilities to query and modify the data. It also has a number of other useful features which will not be covered in the scope of this article.

So if Hasura is already a backed of it’s own, why would we want to integrate it with Node server? Having a node server and Hasura has a number of advantages:

  • You can define your own custom resolvers with NestJS/GraphQL package for custom business logic
  • You can stitch your own GraphQL and Hasura GraphQL schema together, so that when you send a query to your sever if it is a query generated by Hasura, it will be forwarded to Hasura, if it’s a query written by you, it will be executed by Node server
  • You can use TypeORM to design, seed and run migrations on your database which is in turn used by hasura.
  • Write your own JWT authentication logic which will be consumed by Hasura
  • Write Unit and E2E tests

Starting the server

The project provides a docker-compose file in the docker directory to start Hasura and the PostgreSQL db cd into docker directory and run command

docker-compose up -d

This command will start hasura and PostgreSQL in the background. PostgreSQL will be accessible at localhost:5430 and Hasura will be at: http://localhost:8081/

You will also need to install Hasura CLI

.env.dev file will have all the necessary environment variables needed to run the server. Before running the server after you successfully started Hasura and the DB set the following to true:

TYPEORM_SEED=true
TYPEORM_DROP_SCHEMA=true
TYPEORM_SYNCHRONIZE=true
npm run start:dev

This will create the schema and populate the database, now stop the server, set them back to false and set the following:

HASURA_METADATA_RELOAD=true

And start the server again. Now on server start, Hasura metadata should be applied. What is Hasura metadata? It tracks all your tables and their relationships and generates resolvers from them, it also has permission rules. You can find metadata at the at the following path in the project directory: hasura/metadata/tables.yaml

Now go to http://localhost:8081/ where you should see the following:

You should see the generated resolvers, try running the query users and select to return username, you should see 2 users returned

Good, now we’ve seed the DB and are able to query it.

Now go to http://localhost:8081/console/data/schema/public/tables/users/permissions

There are should be two roles defined (if they are not defined, you need to apply the metadata). Users with a role Admin can do everything. Observer role can only query users table and can only select id and username as return variables. After you have seeded the database, the users table should have 2 users: john and admin. John has a role – Observer and admin is respectively – Admin.

How to login

Make sure you’ve started your nodejs server by running npm run start:dev

Go to: http://localhost:4000/graphql. Server port is defined in .env.dev. Where you should see the following

If you see queries like: users and users_aggregate, the schema stitching worked correctly and you are seeing schema from hasura as well as your own schema. Now lets write a login query. First we are going to log in with john user who has a role Observer and I will explain the code that is responsible for forwarding JWT to hasura.

ConfigGraphqlHasuraService is responsible for stitching the schema and forwarding JWT headers to Hasura, the code is self explanatory, and there are comments. Now let’s login as john user.

query {
  login(user: {username: "john", password: "password"}) {
    accessToken
  }
}

Execute the above query in the playground and copy the access token. If you decode the token at jwt.io, you should get the following:

 "sub": "759d7f4c-4331-462b-a3c0-01d2af392f1c",
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": [
      "admin",
      "observer"
    ],
    "x-hasura-default-role": "observer",
    "x-hasura-role": "observer",
    "x-hasura-user-id": "759d7f4c-4331-462b-a3c0-01d2af392f1c"
  },
  "iat": 1606956957,
  "exp": 1606960557
}

Now if we go to our Hasura page, we can pass JWT in the headers and see the result of our permission logic:

Make sure to uncheck x-hasura-admin-secret and add Authorization header. Header should be in format off: Bearer <your encoded jwt token>

If you did everything right, on the left side you should see only users query. Now try to run users query and return username and password

query MyQuery {
  users {
    username
    password
  }
}

You should see the following error returned

{
  "errors": [
    {
      "extensions": {
        "path": "$.selectionSet.users.selectionSet.password",
        "code": "validation-failed"
      },
      "message": "field \"password\" not found in type: 'users'"
    }
  ]
}

Let’s try execute the same query in our playground at http://localhost:4000/graphql

You can add the Authorization header on bottom left in the following format:

{
  "Authorization": "Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4iLCJzdWIiOiI3NTlkN2Y0Yy00MzMxLTQ2MmItYTNjMC0wMWQyYWYzOTJmMWMiLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsiYWRtaW4iLCJvYnNlcnZlciJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJvYnNlcnZlciIsIngtaGFzdXJhLXJvbGUiOiJvYnNlcnZlciIsIngtaGFzdXJhLXVzZXItaWQiOiI3NTlkN2Y0Yy00MzMxLTQ2MmItYTNjMC0wMWQyYWYzOTJmMWMifSwiaWF0IjoxNjA2OTU3MzU5LCJleHAiOjE2MDY5NjA5NTl9.dTSFNrB-BDNDt3Ede7OVk2mOvPua5woKu_57v2NLzxBnW8ukywQHrBLEDfDNDRgoaNW5gJQT5se4ewdIlQpNfw"
}

You should see the same error, username john does not have access to password field. Now let’s login with admin and copy the returned access token

query {
  login(user: {username: "admin", password: "password"}) {
    accessToken
  }
}

You should see two users with password returned. Ofcourse this is just an example, in the real world you would never return a password back to the client. This is just to showcase the authentication and authorization. JWT implementation can be found in AuthModule.

Conclusion

In this article we’ve explored forwarding of JWT to Hasura, stitching GraphQL schemas and populating DB with TypeORM. Very powerful stuff to quickly build backend with GraphQL and NestJS framework.

Source code can be found here: https://github.com/artonio/nest-jwt-hasura

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *