How to Setup Authentication with Django Graphene and Hasura GraphQL

Hasura has a great feature of being able to merge in external GraphQL schemas, allowing us to do things like stitch together a mesh of services powered by GraphQL.

In this walkthrough, we’re going to go through creating a simple GraphQL authentication service using Django Graphene, meshing it into your Hasura service, and creating a few sample requests.

Getting Started

Similar to the Food Network, we can start this off by taking a look at what the final project looks like.

If you pull down this project, and run:

on the directory, you’ll receive a setup like the following:

Pro Tip: If you look in the docker-compose.yml you'll notice we're using the image hasura/graphql-engine:latest.cli-migrations-v2.

This migrations image will automatically apply our Hasura metadata (connected remote schema, tracked tables, permissions) as well as our SQL migrations (to generate our schema) from the ./hasura folders which are mounted as volumes in our docker-compose file.

Migrations are a super-powerful way to work with Hasura instances, and I highly recommend them as a way to work through your deployment work-flow (more info: https://hasura.io/docs/1.0/graphql/core/migrations/index.html)

The Django Service

If we take a look at the ./django/dockerfile you'll see that we're running a standard Python 3 container with:

initiated ( django-graphql-jwt is the package we'll be using as a helper with this project and it comes with graphene-django and PyJWT as dependencies - your can read more about this package here: https://github.com/flavors/django-graphql-jwt).

We’ve also pinned PyJWT to < 2 as there's a compatibility issue with the current django-graphql-jwt and the latest major version of PyJWT.

If you look at the project structure in the ./django folder you'll notice it's the same as if we had run:

and then:

from within our project.

We’re going to be putting our global settings in app and then our API specific functions in api.

We’ve also automatically run make and run migrations in the ./django/entrypoint.sh on each container startup.

Project Setup

Let’s take a look at what we’ve added to our ./django/app/settings.py file:

./django/app/settings.py

In terms of setting up our JWT — we’ve gone with a short expiration time (5 minutes for our access token) and a longer time for our refresh token (7 days).

In general, this setup works well in that we’ll only use our refresh token for updating the expiration time on our access token, and our access token (which gives us access to our application) will frequently be refreshed in case of compromise.

You saw from the above that we’ve mentioned 2 other files, app.utils and app.schema - let's walk through those and look at what they're doing.

./django/app/utils.py

We’ve got a utility function here to encode our Hasura JWT payload based on the spec at: https://hasura.io/docs/1.0/graphql/core/auth/authentication/jwt.html#the-spec

You’ll notice we make reference to api.models and user.profile.role - that's a little different.

Down below we’re going to go over how to extend our user model — once you know how to do that you’ll be able to extend your JWT payload further with any other x-hasura... claims your application requires.

./django/app/schema.py

This sets up our root GraphQL schema file.

You’ll notice we’re declaring token_auth, refresh_token, and verify_token nodes which will be our default methods of logging in, refreshing our token's expiration, and verifying our token.

The will create mutations for those actions.

We also mention api.schema which contains the logic for our other query and mutation nodes which we'll be reviewing below.

Extending the User Model

Before we get to setting up our GraphQL logic, a little housekeeping.

We want to define the role of our user. It’s a really common paradigm in Django and there are a lot of ways to tackle adding more fields to your Django user.

The easiest way is to extend your user model with a one-to-one model.

This creates a new table which has a relationship to Django’s default user model.

./django/api/models.py

What we’re doing here is basically saying, each user has an associated profile row. Roles can be either user or manager (based on active_roles)- defaulting to user.

On creation of a new user - make sure they have a profile entry.

Just to make it nice, we can also add this relationship to our admin section — this will make this profile model available in our Django Admin ( http://localhost:8000/admin/), under our default user model:

./django/api/admin.py

Setting Up Our Schema Functions

We’ve so far created our mutations for logging in, refreshing, and verifying our token using our root schema file.

You’ll remember that we referenced api.schema.Query and api.schema.Mutation inside that root schema file.

Inside this schema file we’ll be defining:

  • A mutation for creating a new user,
  • A query for retrieving my own user profile
  • Another query for retrieving all users

with authentication restrictions for role and if the user is authenticated.

./django/api/schema.py

Tying It All Together

By visiting urls.py, we can ensure that our routes are setup to make our GraphQL API accessible to our Hasura instance.

./django/app/urls.py

Now, if you’re using the docker-compose starter you should already be setup with docker being connected to Hasura — but if not, you can make sure all your containers are running and visit: http://localhost:8080/console/remote-schemas/manage/schemas

From here, you’ll be able to mesh your Graphene GraphQL API to Hasura by entering http://host.docker.internal:8000/graphql as your GraphQL Server URL:

Testing Our Requests

Hasura has a great GraphiQL request tester which can be found here to run through some sample requests and responses — http://localhost:8080/console/api-explorer

Creating a User

Let’s get started with creating a user — we can see some of the nested relationship structures which are possible within GraphQL.

We’ve returned a token here - this token will be available as our access token for the next 5 minutes.

Verify a Token

We’re able to take any token created and test it using verifyToken to validate and receive the token's corresponding payload.

Refreshing a Token

If our token expires, we’re able to refresh the token using refreshToken - this will result in a response with a token with an updated expiry time (+ 5 minutes in this case).

Logging In

Logging in will also provide us with a token / refresh_token pair.

Using Graphene Authentication: Who Am I?

We’ve covered talking with our API to retrieve a token - but what do we do with it now?

If we create a new Authorization header, with Bearer followed by our token, we'll be able to authenticate as a user (pictured below).

We can also select the Decode JWT icon to the right of the field to help us analyze what's being decoded from our entered token.

Running the following query will run our whoami query

Using Graphene Authentication: Manager Role

We’ve tested to make sure our role works, but next let’s check to make sure our Manager role will work to show us all users.

We can create a Django Superuser using the following command to give us access to the Django Admin at http://localhost:8000/admin/ :

From that admin panel if we update our user to the manager role and then re-login, we'll be able to see a view of all users by running:

Where do we go from here?

To start we should start off by changing any secrets that are found in our docker-compose.yml file - you can create a new secret with the following:

But what else can we do once we have an external Django auth service?

  • We can create more remote schema nodes similar to users and whoami above to to extend our API's logic.
  • We can link up our Django service to work with Actions and Events.
  • We can use our JWT implementation to authenticate with our Hasura service and configure row-level permissions using our x-hasura-... JWT claims which were set in the JWT encoder.

If you’d like to talk to us about this article or just connect with the team, you should join our community!

Originally published at https://hasura.io on February 11, 2021.

⚡️ Instant realtime GraphQL APIs! Connect Hasura to your database & data sources (GraphQL, REST & 3rd party API) and get a unified data access layer instantly.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store