Hasura Authentication Explained
Hasura Authentication Explained
dev.to/hasurahq/hasura-authentication-explained-2c95
Vladimir Novick
As you may know from various other blog posts found on blog.hasura.io, Hasura supports
various types of authentication and in the following blog post I want to lay out what are your
authentication options when using Hasura in production.
1/15
This link leads to the docs section describing how to secure your GraphQL endpoint by
passing an environment variable HASURA_GRAPHQL_ADMIN_SECRET . Whether you are using
Docker, Heroku or anything else, that will be the first step you will do.
So now if you try to access the console, you will get a simple "login" page that will ask you to
specify your secret
The name ADMIN_SECRET suggest that if you pass it in your request headers, you will be
giving admin permissions to API consumer. That's why it's important that you use it only
from server to server interactions such as using it from serverless functions etc.
So that is not an actual authentication. That's just securing your endpoint. Now, what about
Authentication and what is the right authentication method for you?
You may want to use Firebase, Auth0, Firebase Functions, Cognito, Your custom auth server,
some unknown auth provider e.t.c
So before looking at different Auth implementations and having lots of blog posts links and
sample apps down below, let's divide our authentication type to two major types
Auth webhooks
2/15
So what is a Webhook? In a nutshell, whenever you set a specific environment variable for
Hasura engine, that includes custom URL, all request headers (unless your webhook is
configured to use GET) will be passed to this custom URL. In your Auth webhook, you can do
whatever you want and it must return either 200 or 401 status codes. Along with 200
status code, you send a bunch of variables prefixed by X-HASURA-* that can be used in
Hasura permission system, that we will discuss later on
We will start by cloning passport-js and following the guide to deploy it on Heroku
Now after it's deployed you can run it with npm start and sending /login or /signup
requests and getting back the username and token
{
"id": 3,
"username": "test123456",
"token": "dd26537df94305f35dca9605b9fade7b"
}%
Now it's time to setup Hasura engine on the database we've just created
3/15
as you can see users table was created when we ran knex migrations when set our
passport-js boilerplate, but we want to use it also from our GraphQL API, so make sure you
click Track button and now you will be able to query your users:
the passport-js example uses LocalStrategy, so a person must authenticate with username
and password, as a result, it will return
{
"id": 3,
"username": "test123456",
"token": "dd26537df94305f35dca9605b9fade7b"
}
The idea is that we need to try to login similar that how we logged in from the console by
hitting the following endpoint https://.herokuapp.com/login
4/15
Setting up Hasura with auth webhook
Now what is left to do is to configure the webhook env variable and set permissions. To add
auth webhook stop our docker container and add HASURA_GRAPHQL_AUTH_HOOK env
variable.
Now what will happen is when you pass Authorization header, it will be passed to custom
auth webhook and processed by it.
Let's take a look at passport-js code:
Let's take a look at a different use case - firebase functions auth hook
The idea is the same - return X-Hasura-User-Id and X-Hasura-Role to be used in Hasura
permissions system. But specifically in this auth webhook we also validate with firebase that
the id_token we've been passed is a correct one
There are other auth-webhooks boilerplate that you can check here.
5/15
Pass headers to Hasura
Headers are forwarded to your custom auth webhook
response returns 200 or 401.
In case of 200 it has to return X-Hasura-* variables to be used in Hasura Permission
system
{
"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"
}
6/15
For example this:
"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"
}
There are other options described in docs how to use JWT, but in a nutshell, it will look like
this:
Any Auth server that returns JWT token have to pass JWT with x-hasura-* claims under
either configured or https://hasura.io/jwt/claims namespace.
Now let's take a look at the case study of the simplest JWT token use case
7/15
Let's go to https://jwt.io/ and choose algorithm as RS256
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"myAmazingAuth": {
"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"
}
}
{
"type":"RS256",
"claims_namespace": "myAmazingAuth",
"key": "copypasted public key"
}
Now we will head to the console and pass our token as Authorization Bearer token.
Our x-hasura-* claims will be extracted from the token and passed to Permissions dialog
where you will be able to set roles, and get really granular access even to specific columns.
8/15
We will talk about Permission system in a bit
Server is passport server with jwt and you can check the code here
Auth0
Auth0 uses JWK urls as well as firebase, so there is a cool tool you can use to auto-generate
your config for either auth0 or firebase. You can check it here
Also for Auth0 you need to configure custom claims in Rules field under Auth0 dashboard
There is a blog post written about Auth0. You can check it here
Firebase
Firebase also uses JWK so HASURA_GRAPHQL_JWT_SECRET will look like that
{"type":"RS512", "jwk_url":
"https://www.googleapis.com/service_accounts/v1/jwk/[email protected]"}
We also need to add custom claims to Firebase, so we will be able to include X-Hasura-*
variables in our encoded token.
There is a great blog post describing the usage of Firebase for authentication.
9/15
Cognito
With AWS Cognito there are several steps you need to do to make it work, so even though I
won't dive deeper in how to do that in this particular blog post, More detailed blog post will
follow. The main idea of using Cognito is similar to Auth0 or Firebase. You need to define
custom claims somewhere. For Cognito, you cannot define that in interface, but you can
create custom Lambda when generating your token like so:
10/15
Our Lambda in this example will be super simple:
11/15
exports.handler = (event, context, callback) => {
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
"https://hasura.io/jwt/claims": JSON.stringify({
"x-hasura-allowed-roles": ["anonymous","user", "admin"],
"x-hasura-default-role": "anonymous",
"x-hasura-user-id": event.request.userAttributes.sub,
"x-hasura-role": event.request.userAttributes.sub === "18cc0fe3-ad0b-44f8-a622-
fd470c7eeb78" ? "admin": "user",
"x-hasura-custom": "custom-value"
})
}
}
}
callback(null, event)
}
That's how we add custom claims. Now you can notice that we pass our claims as stringified
JSON. This is done because Cognito does not support nested custom claims. Also, you can
see that I am checking for specific user if its and admin or not and if it is I return an admin
role.
{
"type":"RS256",
"jwk_url": "https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json",
"claims_format": "stringified_json"
}
And that's basically it. Your Cognito token will be decoded and passed to Hasura permission
system
12/15
The first layer of permissions is roles. Roles are defined based on x-hasura-default-role and
x-hasura-allowed-roles variables passed to the permission system.
Second layer is custom checks based on x-hasura-user-id or any other custom variable
passed to permission system
13/15
As you can see in this example, we are checking post content to be "custom_value" and only
if it is, we enable user to select fields. What it will mean is that user will be able to see posts
only when posts content is equal to "custom value"
14/15
And that will mean that user will be able to see only his/her posts.
Summary
As you can see from the summary above, Hasura supports lots of different Authentication
techniques and is aligned with best practices in industry. In addition to that Hasura
permissions system gives you a really granular level of access control used, which is a must
in production apps.
15/15