Authentication using JWT

Introduction

You can configure GraphQL engine to use JWT authorization mode to authorize all incoming requests to Hasura GraphQL engine server.

The idea is - Your auth server will return JWT tokens, which is decoded and verified by GraphQL engine to authorize and get metadata about the request (x-hasura-* values).

The JWT is decoded, the signature is verified, then it is asserted that the current role of the user (if specified in the request) is in the list of allowed roles. If current role is not specified in the request, then the default role is picked. If the authorization passes, then all of the x-hasura-* values in the claim is used for the permissions system.

Prerequisite

It is mandatory to first secure your GraphQL endpoint for the JWT mode to take effect.

In JWT mode, on a secured endpoint:

  • JWT authentication is enforced when X-Hasura-Admin-Secret header is not found in the request.
  • JWT authentication is skipped when X-Hasura-Admin-Secret header is found in the request and admin access is granted.

TL;DR

  1. The JWT must contain: x-hasura-default-role, x-hasura-allowed-roles in a custom namespace in the claims.
  2. Other optional x-hasura-* fields (required as per your defined permissions).
  3. You can send x-hasura-role as header in the request to indicate a different role.
  4. Send the JWT via Authorization: Bearer <JWT> header.

The Spec

When your auth server generates the JWT, the custom claims in the JWT must contain the following:

  1. A x-hasura-default-role field : indicating the default role of that user
  2. A x-hasura-allowed-roles field : a list of allowed roles for the user

The claims in the JWT, can have other x-hasura-* fields where their values can only be strings. You can use these x-hasura-* fields in your permissions.

Now, the JWT should be sent by the client to Hasura GraphQL engine via the Authorization: Bearer <JWT> header.

Example JWT claim:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["editor","user", "mod"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "1234567890",
    "x-hasura-org-id": "123",
    "x-hasura-custom": "custom-value"
  }
}

This contains standard (sub, iat etc.) and custom (name, admin etc.) JWT claims, as well as Hasura specific claims inside a custom namespace (or key) i.e. https://hasura.io/jwt/claims.

The https://hasura.io/jwt/claims is the custom namespace where all Hasura specific claims have to be present. This value can be configured in the JWT config while starting the server.

Note: x-hasura-default-role and x-hasura-allowed-roles are mandatory, while rest of them are optional.

Note

All x-hasura-* values should be String, they will be converted to the right type automatically.

The default role can be overridden by x-hasura-role header, while making a request.

POST /v1/graphql HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
X-Hasura-Role: editor

...

Configuring JWT mode

You can enable JWT mode by using the --jwt-secret flag or HASURA_GRAPHQL_JWT_SECRET environment variable; the value of which is a JSON object:

{
  "type": "<standard-JWT-algorithms>",
  "key": "<optional-key-as-string>",
  "jwk_url": "<optional-url-to-refresh-jwks>",
  "claims_namespace": "<optional-key-name-in-claims>",
  "claims_format": "json|stringified_json"
}

key or jwk_url, one of them has to be present.

type

Valid values are : HS256, HS384, HS512, RS256, RS384, RS512. (see https://jwt.io).

HS* is for HMAC-SHA based algorithms. RS* is for RSA based signing. For example, if your auth server is using HMAC-SHA256 for signing the JWTs, then use HS256. If it is using RSA with 512-bit keys, then use RS512. EC public keys are not yet supported.

key

  • In case of symmetric key (i.e. HMAC based key), the key as it is. (e.g. - “abcdef…”). The key must be long enough for the algorithm chosen, (e.g. for HS256 it must be at least 32 characters long).
  • In case of asymmetric keys (RSA etc.), only the public key, in a PEM encoded string or as a X509 certificate.

This is an optional field. You can also provide a URL to fetch JWKs from using the jwk_url field.

jwk_url

A URL where a provider publishes their JWKs (which are used for signing the JWTs). The URL must publish the JWKs in the standard format as described in https://tools.ietf.org/html/rfc7517

This is an optional field. You can also provide the key (certificate, PEM encoded public key) as string as well - under the key field.

Rotating JWKs:

Some providers rotate their JWKs (E.g - Firebase). If the provider sends an Expires header with the response of JWK, then graphql-engine will refresh the JWKs automatically. If the provider does not send Expires header, the JWKs are not refreshed.

Example:

  • Auth0 publishes their JWK url at: https://<YOUR_AUTH0_DOMAIN>.auth0.com. But Auth0 has a bug. See known issues: Auth0.
  • Firebase publishes their JWK url at: https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com.

claims_namespace

This is an optional field. You can specify the key name inside which the Hasura specific claims will be present. E.g. - https://mydomain.com/claims.

Default value is: https://hasura.io/jwt/claims.

claims_format

This is an optional field, with only the following possible values: - json - stringified_json

Default is json.

This is to indicate that if the hasura specific claims are a regular JSON object or stringified JSON

This is required because providers like AWS Cognito only allows strings in the JWT claims. See #1176.

Example:-

If claims_format is json then JWT claims should look like:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["editor","user", "mod"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "1234567890",
    "x-hasura-org-id": "123",
    "x-hasura-custom": "custom-value"
  }
}

If claims_format is stringified_json then JWT claims should look like:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "https://hasura.io/jwt/claims": "{\"x-hasura-allowed-roles\":[\"editor\",\"user\",\"mod\"],\"x-hasura-default-role\":\"user\",\"x-hasura-user-id\":\"1234567890\",\"x-hasura-org-id\":\"123\",\"x-hasura-custom\":\"custom-value\"}"
}

Examples

HMAC-SHA based

Your auth server is using HMAC-SHA algorithms to sign JWTs, and is using a 256-bit key. In this case, the JWT config will look like:

{
  "type":"HS256",
  "key": "3EK6FD+o0+c7tzBNVfjpMkNDi2yARAAKzQlk8O2IKoxQu4nF7EdAh8s3TwpHwrdWT6R"
}

The key is the actual shared secret, which is used by Hasura and the external auth server.

RSA based

If your auth server is using RSA to sign JWTs, and is using a 512-bit key. In this case, the JWT config needs to have the only the public key.

Example 1: public key in PEM format (not OpenSSH format):

{
  "type":"RS512",
  "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----\n"
}

Example 2: public key as X509 certificate:

{
  "type":"RS512",
  "key": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIINw9gva8BPPIwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgQt7dIsMTIU9k1SUrFviZOGnmHWtIAw\nmtYBcM9I0f9/ka45JIRp5Y1NKpAMFSShs7Wv0m1JS1kXQHdJsPSmjmDKcwnBe3R/\nTU3foRRywR/3AJRM15FNjTqvUm7TeaW16LkkRoECAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBADfY2DEmc2gb8/pqMNWHYq/nTYfJPpK4VA9A0lFTNeoq\nzmnbGwhKj24X+Nw8trsvkrKxHvCI1alDgBaCyzjGGvgOrh8X0wLtymp1yj6PWwee\nR2ZPdUaB62TCzO0iRv7W6o39ey+mU/FyYRtxF0ecxG2a0KNsIyFkciXUAeC5UVDo\nBNp678/SDDx9Ltuxc6h56a/hpBGf9Yzhr0RvYy3DmjBs6eopiGFmjnOKNxQrZ5t2\n339JWR+yiGEAtoHqk/fINMf1An6Rung1xYowrm4guhCIVi5unAvQ89fq0I6mzPg6\nLhTpeP0o+mVYrBmtYVpDpv0e71cfYowSJCCkod/9YbY=\n-----END CERTIFICATE-----"
}

Example 3: public key published as JWKs:

{
  "type":"RS512",
  "jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"
}

Running with JWT

Using the flag:

$ docker run -p 8080:8080 \
    hasura/graphql-engine:latest \
    graphql-engine \
    --database-url postgres://username:password@hostname:port/dbname \
    serve \
    --admin-secret myadminsecretkey \
    --jwt-secret '{"type":"HS256", "key": "3EK6FD+o0+c7tzBNVfjpMkNDi2yARAAKzQlk8O2IKoxQu4nF7EdAh8s3TwpHwrdWT6R"}'

Using env vars:

$ docker run -p 8080:8080 \
    -e HASURA_GRAPHQL_ADMIN_SECRET="myadminsecretkey" \
    -e HASURA_GRAPHQL_JWT_SECRET='{"type":"RS512", "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----\n"}' \
    hasura/graphql-engine:latest \
    graphql-engine \
    --database-url postgres://username:password@hostname:port/dbname \
    serve

Generating JWT Config

The JWT Config to be used in env HASURA_GRAPHQL_JWT_SECRET or --jwt-secret flag can be generated using: https://hasura.io/jwt-config.

Currently the UI supports generating config for Auth0 and Firebase.

The config generated from this page can be directly pasted in yaml files and command line arguments as it takes care of escaping new lines.

Auth JWT Examples

Here are some sample apps that use JWT authorization. You can follow the instructions in the READMEs of the repositories to get started.