Common access control patterns with Hasura GraphQL Engine
Tags: hasura, graphql, postgres, accesscontrol
Published: 2019-05-23 18:22:36

---
title: Common access control patterns with Hasura GraphQL Engine
published: true
description: In this post, we will look at some access control patterns that can be used with Hasura to granularly allow/restrict the data
tags: hasura, graphql, postgres, access-control
cover_image: https://blog.hasura.io/content/images/2019/05/acl-blog-banner.png
canonical_url: https://blog.hasura.io/access-control-patterns-with-hasura-graphql-engine/
---

## Introduction

In this post, we will look at some access control patterns that can be used with [Hasura](https://github.com/hasura/graphql-engine) to granularly allow/restrict the data. This is a summary blog post from [Hasura Streams](https://twitch.tv/hasurahq) and the video has been uploaded to [Youtube](https://www.youtube.com/hasurahq). If you prefer watching, similar examples have been covered in this video:

[![video link](https://thepracticaldev.s3.amazonaws.com/i/dtvgovo3cjd0dxcnov55.png)](https://www.youtube.com/watch?v=wrBOKlVG69A)

## Hasura GraphQL Engine

> [Hasura GraphQL Engine](https://github.com/hasura/graphql-engine) is a thin GraphQL server that sits on any Postgres database and allows you to CRUD the data with realtime GraphQL and access control

This post assumes that you have basic understanding of Hasura and relational data models. [Check out](https://docs.hasura.io/1.0/graphql/manual/getting-started/index.html) this guide if you have never used Hasura before.

Hasura enables role based access control which can be integrated with most Auth providers. The access control rules in Hasura are functions of session variables. Session variables are x-hasura-* variables like x-hasura-role, x-hasura-user-id that can be decoded from the request headers of the GraphQL request. You could have any number of session variables to make the rules more granular.

![auth flow](https://blog.hasura.io/content/images/2019/05/auth-high-level-overview1.png)

Let us see how to set up access control rules as functions of these session variables. Hasura infers the GraphQL schema from the Postgres schema, which means that setting access control rules on the tables, columns and their relationships corresponds to setting access control rules on the fields of your GraphQL schema.

## Permissions

### UI

The Hasura console has a neat UI for setting permissions. It also has a filter-builder that will make building filters and checks very joyful.

![perm-ui](https://blog.hasura.io/content/images/2019/05/insert_perm_example.png)

### Roles

With each GraphQL request made to it, Hasura checks the role of the client user in the session variable `x-hasura-role`. Looking at this role, Hasura looks for the permissions for this role and allows or restricts CRUD accordingly.

> The roles in Hasura have nothing to do with the Postgres roles and users. These roles are implemented at the Hasura Layer

You can define these roles in the Hasura console and there is no limit on multiple roles being defined.

### Insert Permission

The permissions to insert rows in a table is composed of three parts:

- **Check constraint**: This is a boolean value built out of session variables, values of the fields of the row being inserted and all the logical operators. For example, to insert into users table, you want to set a condition like `{ "id": {  "_eq": "x-hasura-user-id" } }`. This condition enforces that whenever a row is being inserted to the users table, it can only be inserted if the `id` of the row is equal to the `x-hasura-user-id` value in the session variables.
- **Columns**: You can restrict insertion to only particular columns (rest of them being null, default or being inferred from `column presets`)
- **Column Presets**: Column presets are values that you can set to be assigned to fields while they are being inserted. For example, when an entry is being inserted in a users table, we can take the `id` from `x-hasura-user-id` session variable. This helps to avoid parsing the session information on the client.

### Select Permission

- **Filter**: This is a boolean value built out of session variables and the values of the fields being selected. For example, in an articles table, you want all users to be allowed to query all published articles, but only their own unpublished articles. To implement that, you would add a filter like:

```json
{
  "or": [
    {
      "author_id": {
        "_eq": "x-hasura-user-id"
      }
    },
    {
      "is_published": true
    }
  ]
}
```

- **Columns**: Sometimes you want some roles to not have access to particular columns of the table. In such cases, you can explicitly choose which columns to allow and restrict.

### Update Permission

- **Filter**: This is a boolean value built out of session variables and the values of the fields being updated. For example, in an `articles` table, you want users to modify only their own articles. To do that, your update filter would look like:

```json
{
  "author_id": {
    "_eq": "x-hasura-user-id"
  }
}
```

- **Columns**: You can restrict which columns can be updated. This comes handy in cases when you do not want `created_at` field to ever be updated, but you might want to update the `title` field in the articles table.

- **Column presets**: Like with insert, if you want to automatically update certain fields with certain values without it being explicitly mentioned in the request.

### Delete Permission

- **Filter**: Like with update, the filter on delete permisson is a boolean condition which needs to be satisfied before the the row can be deleted.

## Examples

With the above ideas, let us look at some specific cases and how they can be modelled. Firstly, lets use a base schema for a [HackerNews](https://news.ycombinator.com) like application. This is the postgres schema:

![schemaspy-schema](https://blog.hasura.io/content/images/2019/05/acl-schemaspy.png)

Let me show you a sample GraphQL query to get all the articles along with their authors, comments, upvotes and downvotes.

```graphql
query {
  articles {
    id
    title
    content
    content_type
    author {
      id
      name
    }
    comments {
      body
      id
      author {
        id
        name
      }
    }
    article_upvotes_aggregate {
      aggregate {
        count
      }
    }
    article_downvotes_aggregate {
      aggregate {
        count
      }
    }
  }
}
```

Now let us try to target some specific access control use cases with the above schema.

### Enforcing users to insert articles as themselves

Say I am a user with `id` = `123`. This means that the value of `x-hasura-user-id` in my session information would be `123`. Now, when I am inserting an article in the `articles` table, you would want the `author_id` to be `123`, which means you would want me to insert the article with myself as the author. To enforce that, you could take two approaches:

- **Through check constraint**: In the insert permission of the `articles` table, you can set a check constraint like `{ "author_id": { "_eq": "x-hasura-user-id" } }`. This would ensure that the article would be inserted only if the `author_id` in the insert payload matches the `x-hasura-user-id` in the session variables.

- **Through column presets**: In the insert permission of the `articles` table, you can disable inserting into `author_id` column and set a column preset such that the `author_id` is automatically taken from the `x-hasura-user-id` session variable. In this way, the `author_id` would be inserted appropriately without the client needing to explicitly mention it.

You can use exactly the same approach for restricting users from updating `articles` that are not published by them.

### Multiple roles

Say you have two roles: `user` and `moderator`. A `moderator` should be allowed to update the `title` of every post unconditionally while a user should be allowed to update the `title` only for their own posts. So the update permission for:

- `moderator`: Should be allowed to update `title` and `content` of without any filter.
- `user`: Should be allowed to update the `title` and `content` with the filter `{ "author_id": { "_eq": "x-hasura-user-id" } }`.

Also, the `moderator` should be allowed to delete every post while a user should be allowed to delete only their own posts. So the delete permission for:

- `moderator`: Should be allowed to delete without checks.
- `user`: Should be allowed to delete  with the filter `{ "author_id": { "_eq": "x-hasura-user-id" } }`

### Access control through views

In a typical news discussion app, the downvotes or upvotes on an article must be anonymous. This means, that the users should not be able to get all the information from `article_downvotes` and `article_upvotes` tables. To achieve anonymity, you can create a view that just has the `article_id` and the number of upvotes or downvotes. For `article_downvotes`, the view would look like:

```
CREATE VIEW article_downvote_count as
SELECT article_id,
  count(*) AS downvotes
FROM article_downvotes
GROUP BY article_id;
```

Now, once this view is created, you can set a select permission on this view such that it can be selected without any checks. You can implement the restriction similarly for `article_upvotes` table as well.

### Downvoting based on Karma

In a website like [HackerNews](https://news.ycombinator.com), you get down-voting privilege only when you reach a particular Karma (say 500). So on a table like `article_downvotes` which is a many to many relationship between `articles` and `users`, your insert permission would be:

```json
{
  "and": [
    {
      "user" : {
        "id": {
          "_eq": "x-hasura-user-id"
        }
      }
    },
    {
      "user": {
        "karma": {
          "_gte": 500
        }
      }
    }
  ]
}
```

The above insert permission ensures that:

- The user is inserting an entry as themselves (`author_id` is equal to `x-hasura-user-id`)
- The insert is successful only if the inserting user has karma greater than or eual to 500.

### Enforcing fields to have only particular values

You can emulate enum-like behavior using Hasura Permissions. For example, in the `articles` table, the `content_type` field should be either `"url"` or `"text"`. Any other value is invalid. Therefore, the check condition of the insert permission on `articles` table looks like:

```json
{
  "_and": [
    {
      "user" : {
        "id": {
          "_eq": "x-hasura-user-id"
        }
      }
    },
    {
      "content_type": {
        "_in": ["text", "url"]
      }
    }
  ]
}
```

This permission makes sure that:

- The user is allowed to insert only as themselves. (`author_id` is equal to `x-hasura-user-id`)
- In the article being inserted, the `content_type` field must be either `"text"` or `"url"`.


---

You can similarly add different complicated access control rules for different roles and secure your data. Let us know if you have any questions in comments or join our Discord channel where we are super active. Also, let us know if you need more examples in the comments.

## Reference

- [Docs](https://docs.hasura.io/1.0/graphql/manual/auth/index.html)
- [GraphQL Engine](https://github.com/hasura/graphql-engine/)
- [Video](https://www.youtube.com/watch?v=wrBOKlVG69A)