If you're developing an API today for consumption by third-parties, you'd be more than forgiven for choosing the REST interface to develop the API, as it is a tried and tested standard for delivering data to consumers, as well as accepting changes to the data in a structured way. In more recent times, GraphQL has become an attractive alternative for developing APIs thanks to its flexible, semantic, and performant nature.
But what if you'd like finer control over the details of the data exchange, including integration with third-party authorization servers using JWTs, and being able to quickly and easily set role-based permissions at the field level?
That's what Hasura have achieved with their GraphQL platform that generates a GraphQL API over a PostgreSQL database. To highlight just a few of the features:
- A PostgreSQL database schema can be designed and populated with data, using the built-in interface
- The database can be queried using the GraphQL syntax, as well as the ability to view the GraphQL schema documentation from within the Hasura admin console
- Roles can be assigned to individual tables and fields, as well as assigned different roles and permissions to individual operations such as selecting or inserting data
- Authorization can be conducted using JSON Web Tokens, issued by a third-party authorization server such as Auth0
“The Hasura platform takes advantage of JWT and GraphQL technologies to provide a platform for querying and manipulating data in a PostgreSQL database, taking into account the role specified in the authorization token.”
Tweet This
A Short Introduction to JSON Web Tokens
From jwt.io:
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
What this looks like in practice is a string that is made up of three parts separated by a period:
- The first part defines the header, containing information such as the signing algorithm used
- The second part is the main body of the token and includes all the claims. This could be things like the user name, expiry date, issue date, and email address
- The last part is the signature, which is derived by cryptographically signing the header and the body together to ensure that the data has not been tampered with
A JWT is issued as a result of a user authenticating with an authorization server. In the case of the Hasura platform, this JWT can then be used to authorize the request and as such can make queries and mutations depending on the claims present in the token. We will see how this works in the demo a little bit later in the article.
The compact nature of a JWT makes it easy to pass around in query strings, headers, and request bodies, or store in a cookie.
What is GraphQL? An Overview
GraphQL is essentially a query language that allows you to define your API in terms of types. It is datastore-agnostic and has server-side support for many different platforms and languages. Instead of defining your API in terms of status codes and semantic URLs, you define the types and then construct queries using a familiar but powerful syntax. As an example:
{ article { id, title, author { name } } }
This describes a query for articles, returning the id, title, and the author's name. As you can see, it supports nested database objects (the author is another table in the schema, linked to from an article). Mutations can also be defined, which describe how to change data.
In addition, multiple queries can be made in a single request, which is most useful when a client wants to retrieve lots of possibly unrelated data points from the server.
The Hasura Approach
The Hasura platform takes advantage of these technologies to provide a platform for querying and manipulating data in a PostgreSQL database, taking into account the role specified in the authorization token.
The Hasura console allows the user to create a database, define its schema, and populate it with data. Hasura will then generate a GraphQL API on top of this database, automatically generating the appropriate types, queries, and mutations that allow the client to completely query for and manipulate the underlying data.
Hasura takes a role-based approach to authorization, and as such an individual role's access can be narrowed down to only a subset of the overall schema (even down to the field level). As each role is mapped to certain authorization rules, it can even be prevented from running certain mutations if so desired. Furthermore, queries can be delegated to an upstream service based on the claims present in the authorization token.
Custom claims inside the JWT are used to tell Hasura about the role of the caller, so that Hasura may enforce the necessary authorization rules to decide what the caller can and cannot do.
[Source]
An example of the claims used by Hasura may look like the following:
{ "https://hasura.io/jwt/claims": { "x-hasura-default-role": "anonymous", "x-hasura-allowed-roles": [ "user", "anonymous" ] }
These two keys define the possible roles as well as the default role in the absence of the "X-Hasura-Role" header. In the case of Auth0, these custom claims can be put in place using Rules, a platform feature that can be used to dynamically enhance tokens as users are authenticated.
Demo: Querying Data
Let's have a look at how authorization with Hasura and JWTs works in practice. A demo has been set up that will allow you to construct queries and see the role-based permissions system in action.
This demo has been pre-configured with a database that has authors, articles, tags, and a relationship table that joins many articles to many tags. Let's see how we can query for some of this data.
Executing anonymous queries
Browse to https://auth0-hasura-demo.herokuapp.com, and use "hasurademo" as the access key.
Next, you will be presented with the Hasura console:
The interface gives you the opportunity to specify some request headers that will be used when querying the GraphQL interface. To demonstrate how the user's role affects their ability to query and modify data, remove the
X-Hasura-Access-Key
header by clicking on the cross on the right-hand side, and enter a new header X-Hasura-Role
with a value of "anonymous":Now, let's make a query. Use the following query inside the Graphiql query entry area in the lower half of the screen:
{ article { id, title } }
To run the query, press Ctrl+Enter (Cmd+Enter if you're on a Mac), or click on the large 'play' button above the query panel. You will see the results of the query on the right-hand side of the screen; you have successfully managed to retrieve the results for two articles that are currently in the database!
Now let's try a mutation. Alter the query so that it executes the following mutation:
mutation { insert_tag(objects:[{name:"<YOUR TAG NAME HERE>"}]) { returning { name } } }
This is a mutation that inserts a new tag with your specified tag name into the database. You should replace the value
<YOUR TAG NAME HERE>
with a value of your choosing.Running this mutation right now will return an error, letting you know that the mutation does not exist.
This is correct since the permissions have effectively been set up so that the anonymous role cannot make changes to the database. This is also indicated by the GraphQL API docs, as it does not show any mutations that can be called by this role. To see this, click the "< Docs" button on the right-hand side of the query window to open the API documentation.
Only queries and subscriptions are available as root types here, but no mutations.
Let's have a closer look at how permissions are set within the Hasura interface.
Validating query permissions
To view the permissions matrix for the "tag" table:
- Click the "data" tab at the top of the screen
- Click the "tag" table on the left-hand side
- Click the "permissions" tab in the main part of the interface
You will arrive at a matrix that describes all the role permissions for the "tag" table. On this screen we can see a number of things:
- The "anonymous" role can select data (denoted by the tick icon in the "select" column), but it can't insert, update, or delete (indicated by the cross icon in the relevant columns)
- The "user" and "admin" roles can do everything
You can see here that, since the "anonymous" user cannot modify or insert any data, it makes sense that there are no mutations available.
Let's now switch to the "user" role and see how we can make this mutation work.
Executing Authorized Queries
Click the "Graphiql" heading at the top of the screen to return to the query interface. Now, change the "X-Hasura-Role" request header so that its value is "user".
If you try to run the mutation again now, you will notice that it will fail for the same reason as before. This is because the "user" role needs to also provide a valid JWT in order to be properly authorized to run this mutation.
“Learn how to create a GraphQL API and secure it using JSON Web Tokens using the @HasuraHQ platform”
Tweet This
Generating a valid JWT
For the purposes of this demo, a tool has been provided to make it easy for you to generate a JWT and run this mutation.
Browse to https://auth0-hasura-demo.now.sh/ and log in with your Auth0 account. If you do not currently have one, you can register for a free account.
Note: In a real-world application, you would retrieve an access token directly from the authorization server when the user logs in, and use that in your requests to the GraphQL engine. This tool has been provided only as a convenience for the purposes of this demo.
Once logged in, you will be able to see your token. Select the entire token string and press Ctrl+C (or Cmd+C on a Mac) to copy it into your clipboard.
Before we head back to the Hasura console, let's inspect the token to see if it contains the claims that we inspect. Head to http://jwt.io, scroll down and paste the token into the left-hand side of the screen. The right-hand side will automatically update to show you the data contained within the token, and you should be able to see that the correct Hasura claims have been included:
With the token still in your clipboard, head back to the Hasura console. The way we attach this token to the request is through the standard "Authorization" request header.
Add a new header into the "headers" section, specifying the Authorization header and the JWT that you have in your clipboard as the bearer token:
Next, try running the mutation again. You should find that the query now works and that the name of the tag is being played back to you in the response:
Note: The "tag" database uses the tag name as its primary key. Since this demo is a cloud deployment that could potentially be used by many people, you should use your own made-up name for a tag as you cannot enter duplicate primary keys. If you have used a tag name that already exists, you will receive an error result in the query results window. Try running the mutation again with a different tag name.
Before we head away from this screen, let's expand the API docs again to show that the "mutations" root now appears and that you can browse through all the available mutations given that you are now authorized to do so:
Finally, to prove that the mutation has worked we can explore the raw data in the database. Once again, click the "Data" heading at the top of the screen and then click on the "tags" table on the left.
The resulting screen will show you the data that is present in the table, and you can see that the "example-tag" entry that was created by the mutation is there.
Wrapping Up
In this post, you learned a bit about GraphQL and the main differences from a traditional REST API. You also had an introduction to JSON Web Tokens, how they are made up, and how easy they are to pass around.
Next, you discovered how Hasura tackles the idea of a role-based architecture when it comes to securing GraphQL APIs, and how it uses JWTs to authorize incoming requests and grant permissions to specific areas of the schema, even down to the field level.
Finally, you had a practical look at the Hasura platform, learning how to perform anonymous queries, and how to perform authorized mutations using a JWT that was issued to you by Auth0.
Further reading
About Auth0
Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.
About Hasura
Based out of Bangalore and San Francisco, Hasura makes developer tools that simplify and accelerate product development.
The Hasura GraphQL engine is an open-source service that gives you instant, realtime, high-performance GraphQL for any Postgres app (including on existing databases).
Read more at Hasura.io.
About the author
Steve Hobbs
Developer Experience Engineer