How to secure GraphQL APIs with Hasura Cloud

In this post, we will look at how to secure GraphQL APIs with various defense mechanisms available in Hasura Cloud. Without the right protections, you are opening up your application network to malicious attacks and potentially exposing data in some cases.

Why security in GraphQL is different?

GraphQL is fundamentally different from REST APIs in the way it is served - API is served over a single endpoint. This means that URL based filtering cannot be applied to GraphQL APIs. Morever REST APIs rely on request methods like GET, POST, PUT and DELETE where as GraphQL is typically served over POST (or websockets for realtime). Attackers can crawl GraphQL endpoint (typically served at /graphql) and exploit interface consoles like GraphiQL if not protected behind auth.

There's more to this, but this gives us an initial idea of why the approach to GraphQL needs to be different.

Let's begin by looking at what constitutes a GraphQL query:

query {
  user(id: 1) {
    id
    name
  }
}

The above is the GraphQL document and the full http request/response would look something like the one below:

GraphQL API Call

The JSON body with the query object will dictate what is to be returned back to the client. So can I ask for whatever I want from the server? Hmm, ideally not. Let's look at various attack vectors below.

Introspection

GraphQL Introspection queries expose the underlying schema and lets you try out all combinations of queries/mutations/subscriptions that are available in the endpoint. Although this is useful during development, it is highly recommended to disable Introspection in production. There's one thing to notice though. The schema exposed via Introspection queries is typically protected behind auth and hence not everything is exposed by default.

With Hasura it is possible to enforce this through Allow List. Once an Allow List is enabled, only the queries explicitly pre-approved will work. So in case, you want introspection queries to work, you will need to explicitly add them to the list. Once this is configured, you don't need to worry about exposed UI interfaces like GraphiQL.

Allow List can be populated by selecting the actual queries made to the server instead of manually entering them.

Allow Lists in Hasura
Allow Lists in Hasura

Authentication

Authentication in GraphQL is not much different than what you would typically do in REST APIs. You can authenticate with any provider (email/password, social sign-in) and pass in Authorization header on every request. Hasura supports Authentication via different methods.

This is infact the pre-GraphQL layer and any production ready Authentication system should work well with GraphQL (and Hasura) to fix this security level.

Authorization

Now comes the tricky bit, Authorization. Hasura supports role-based access control at row and column level of your database schema.

Role based permission system Hasura
Role based permission system with Hasura

Taking the example of the above GraphQL query, we can select which fields are allowed to be queried and which role is allowed to query.

Hasura supports boolean expressions to configure row level checks and this makes it powerful. For example, we can traverse through relationships to make a check and also combine multiple conditions using operators like _and and _or.

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

Read more examples of using Hasura's permission system effectively or build a slack clone using Hasura's Authorization layer.

Admin Secret

To make sure that your GraphQL endpoint and the Hasura console are not publicly accessible, you need to configure an admin secret key. If you want to give restricted access to the console in an organisation team setup, you can add collaborators to your Hasura Cloud Project and give them access to only view the metrics of the project and not allow GraphQL API access.

Read more on how to add an admin secret to a Hasura project.

Tip: Do not ever use an admin secret on a frontend client.

API Limits

Public facing APIs are subject to DDoS attacks. To prevent abuse, we need to limit the number of API calls being made for a particular GraphQL query or any query. In some cases, this will be applicable for a role and in some case this will be applicable for a given user id.

Role based: API limits can be configured at the role level i.e the number of requests per minute that can be made by a given role.

Parameter based: To make it more granular, you can also specify parameters like IP address and session variables like x-hasura-user-id or custom x-hasura- variables to filter.

Depth based: Depending on the relationship depth, the graphql operation can go n level deep and can create a really complex SQL join and slow down the API responses. Based on the application requirement, the maximum depth of a graphql operation should have a reasonable limit to prevent abuse.

Configure GraphQL API Limits per role
Configure GraphQL API Limits per role

Some of these settings are a bonus if Allowed List is configured right, since most of the filtering happens at that layer.

External APIs

Apart from the instant GraphQL APIs, you have custom business logic written in different servers that are added to Hasura via Actions, Remote Schemas and Events. For example with Actions and Event triggers, the REST API configured can be called directly if not protected by a shared secret between Hasura and the API server. In case of a remote schema, appropriate authorization header needs to be configured for the initial loading of the schema.

But to ensure granular permissions, Forwarding client headers can be enabled and the API server can handle the request based on respective client.

Restrictive CORS

By default, Hasura allows all CORS requests. For example, if your application is hosted on a domain, say https://example.com, you can allow any requests to come from this and any of its subdomains by enabling the config HASURA_GRAPHQL_CORS_DOMAIN="http://*.example.com"
Of course this restriction applies only on the client side (browser).

Timeouts

The other attack vector is to make a query which takes a really long time to execute. There are broadly two types of timeouts:

Database timeout: In case a database connection or a query takes a long time to respond, Hasura can return a timeout error. There's a statement_timeout setting in Postgres to abort any statement execution if it's taking longer than specified milliseconds.

HTTP timeout: If you are using Actions/Remote Schemas/Events, you would have configured http endpoints for respective requests. You can configure a timeout setting as well to abort the query execution. Http endpoints make different queries in their underlying logic and configuring a timeout is recommended to abort connections and respond quickly back to the client.

Hasura's production check-list is a good resource to go over to address top level security concerns before going live with your application.

Resources:

Blog
02 Dec, 2020
Email
Subscribe to stay up-to-date on all things Hasura. One newsletter, once a month.
Loading...
v3-pattern
Accelerate development and data access with radically reduced complexity.