How to Setup Authentication with Django Graphene and Hasura GraphQL
Tags: django, jwt, remoteschemas
Published: 2021-02-16 05:29:51

---
title: How to Setup Authentication with Django Graphene and Hasura GraphQL
published: true
date: 2021-02-11 17:10:09 UTC
tags: Django,JWT,RemoteSchemas
canonical_url: https://hasura.io/blog/how-to-setup-authentication-with-django-graphene-and-hasura-graphql/
---

 ![How to Setup Authentication with Django Graphene and Hasura GraphQL](https://hasura.io/blog/content/images/2021/02/django_hasura-1.png)

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](https://github.com/martin-hasura/blog-django-graphql-hasura), and run:

```
docker-compose up

```

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

![How to Setup Authentication with Django Graphene and Hasura GraphQL](https://hasura.io/blog/content/images/2021/02/container-layout.png)<figcaption>Project Container Layout</figcaption>

**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](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:

```
pip install django-graphql-jwt django

```

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](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:

```
django-admin startproject app 

```

and then:

```
python manage.py startapp api

```

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

```
...
# Lets us load our secret from our docker-compose env variables
SECRET_KEY = os.environ['DJANGO_SECRET']
...
# Make sure our Django instance is accessible locally
ALLOWED_HOSTS = ['localhost', 'host.docker.internal']
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
# We're going to load:
# - Graphene
# - Optional refresh_token to use a token / refresh scheme in our API
# - Our api app (which contains our API functions)
    'graphene_django',
    'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
    'api',
]
...
# We're going to use an external SQLite DB for this demo, but if you wanted to use the same Postgres as your app DB, you would use similar to the below and this DB would be accessible in Hasura:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    },
    # 'default': {
    # 'ENGINE': 'django.db.backends.postgresql',
    # 'NAME': 'postgres',
    # 'USER': 'postgres',
    # 'PASSWORD': 'password',
    # 'HOST': 'blog-postgres',
    # 'PORT': '5432',
    # }
}
...
# Defines what Graphene settings and JWT middleware
# You'll notice we've defined the app.schema - we'll walk through a review of this below.
GRAPHENE = {
    'SCHEMA': 'app.schema.schema',
    'MIDDLEWARE': [
        'graphql_jwt.middleware.JSONWebTokenMiddleware',
    ],
}
...
# Defines JWT settings and auth backends
# You'll notice we've defined app.utils.jwt_payload - we'll be reviewing it below as well.
GRAPHQL_JWT = {
    'JWT_PAYLOAD_HANDLER': 'app.utils.jwt_payload',
    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LONG_RUNNING_REFRESH_TOKEN': True,
    'JWT_EXPIRATION_DELTA': timedelta(minutes=5),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),    
    'JWT_SECRET_KEY': os.environ['DJANGO_SECRET'],
    'JWT_ALGORITHM': 'HS256',
}
AUTHENTICATION_BACKENDS = [
    'graphql_jwt.backends.JSONWebTokenBackend',
    'django.contrib.auth.backends.ModelBackend',
]

```

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

```
import jwt
import api.models
from datetime import datetime
from graphql_jwt.settings import jwt_settings
    
## JWT payload for Hasura
def jwt_payload(user, context=None):
    jwt_datetime = datetime.utcnow() + jwt_settings.JWT_EXPIRATION_DELTA
    jwt_expires = int(jwt_datetime.timestamp())
    payload = {}
    payload['username'] = str(user.username) # For library compatibility
    payload['sub'] = str(user.id)
    payload['sub_name'] = user.username
    payload['sub_email'] = user.email
    payload['exp'] = jwt_expires
    payload['https://hasura.io/jwt/claims'] = {}
    payload['https://hasura.io/jwt/claims']['x-hasura-allowed-roles'] = [user.profile.role]
    payload['https://hasura.io/jwt/claims']['x-hasura-default-role'] = user.profile.role
    payload['https://hasura.io/jwt/claims']['x-hasura-user-id'] = str(user.id)
    return payload

```

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](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

```
import graphene
import graphql_jwt
import api.schema

## Mutation: 
# - token_auth - for Login
# - refresh_token - for Token refresh
# + schema from api.schema.Mutation
class Mutation(api.schema.Mutation, graphene.ObjectType):
    token_auth = graphql_jwt.ObtainJSONWebToken.Field()
    refresh_token = graphql_jwt.Refresh.Field()
    verify_token = graphql_jwt.Verify.Field()    
    pass

## Query:
# + schema from api.schema.Query
class Query(api.schema.Query, graphene.ObjectType):
    pass

# Create schema
schema = graphene.Schema(query=Query, mutation=Mutation)

```

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

```
from django.db import models
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

# Create your models here.
active_roles=(
    ("user", "user"),
    ("manager", "manager")
)

class profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    role = models.CharField(max_length=120, choices=active_roles, default="user")
    
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

```

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/](http://localhost:8000/admin/)), under our default user model:

### ./django/api/admin.py

```
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from api.models import profile

# Register your models here.
# Inline + descriptor
class AdminProfileInline(admin.StackedInline):
    model = profile
    can_delete = False
    verbose_name_plural = 'profiles'

# Define new admin with inline
class ProfileUserAdmin(BaseUserAdmin):
    inlines = (AdminProfileInline,)

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, ProfileUserAdmin)

```

## 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

```
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
from api.models import profile
from graphql_jwt.shortcuts import create_refresh_token, get_token
import graphene
import graphql_jwt

## Mutation: Create User
# We want to return:
# - The new `user` entry
# - The new associated `profile` entry - from our extended model
# - The access_token (so that we're automatically logged in)
# - The refresh_token (so that we can refresh my access token)

# Make models available to graphene.Field
class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()

class UserProfile(DjangoObjectType):
    class Meta:
        model = profile

# CreateUser
class CreateUser(graphene.Mutation):
    user = graphene.Field(UserType)
    profile = graphene.Field(UserProfile)
    token = graphene.String()
    refresh_token = graphene.String()
    
    class Arguments:
        username = graphene.String(required=True)
        password = graphene.String(required=True)
        email = graphene.String(required=True)

    def mutate(self, info, username, password, email):
        user = get_user_model()(
            username=username,
            email=email,
        )
        user.set_password(password)
        user.save()

        profile_obj = profile.objects.get(user=user.id)        
        token = get_token(user)
        refresh_token = create_refresh_token(user)
        
        return CreateUser(user=user, profile=profile_obj, token=token, refresh_token=refresh_token)
    
# Finalize creating mutation for schema
class Mutation(graphene.ObjectType):
    create_user = CreateUser.Field()
  
## Query: Find users / my own profile  
# Demonstrates auth block on seeing all user - only if I'm a manager
# Demonstrates auth block on seeing myself - only if I'm logged in
class Query(graphene.ObjectType):
    whoami = graphene.Field(UserType)
    users = graphene.List(UserType)
    
    def resolve_whoami(self, info):
        user = info.context.user
        # Check to to ensure you're signed-in to see yourself
        if user.is_anonymous:
            raise Exception('Authentication Failure: Your must be signed in')
        return user
    
    def resolve_users(self, info):
        user = info.context.user
        print(user)
        # Check to ensure user is a 'manager' to see all users
        if user.is_anonymous:
            raise Exception('Authentication Failure: Your must be signed in')
        if user.profile.role != 'manager':
            raise Exception('Authentication Failure: Must be Manager')
        return get_user_model().objects.all()

```

## 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

```
from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView

urlpatterns = [
    path('admin/', admin.site.urls), # Exposes your django admin at http://localhost:8000/admin/
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=False))), # Exposes your django admin at http://localhost:8000/graphql
]

```

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](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:

![How to Setup Authentication with Django Graphene and Hasura GraphQL](https://hasura.io/blog/content/images/2021/02/connect-graphene.png)<figcaption>Connecting Graphene and Hasura</figcaption>

## 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](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.

```
mutation createUser {
  createUser(username: "<your_username>", password: "<your_password>", email: "<your_email>") {
    refreshToken
    token
    user {
      username
    }
    profile {
      role
    }
  }
}

```

### 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.

```
mutation verifyToken {
  verifyToken(token: "<your_token>") {
    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).

```
mutation refreshToken {
  refreshToken(refreshToken: "<refresh_token>") {
    token
  }
}

```

### Logging In

Logging in will also provide us with a token / refresh\_token pair.

```
mutation loginUser {
  tokenAuth(username: "<username>", password: "<password>") {
    token
    refreshToken
    payload
  }
}

```

### 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).

![How to Setup Authentication with Django Graphene and Hasura GraphQL](https://hasura.io/blog/content/images/2021/02/authorization.png)<figcaption>Configuring Authorization Headers</figcaption>

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

```
query whoami {
  whoami {
    email
  }
}

```

### 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/](http://localhost:8000/admin/) :

```
docker exec -it blog-django bash -c 'python manage.py createsuperuser'

```

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:

```
query userList {
  users {
    email
  }
}

```

## 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:

```
docker exec blog-django bash -c 'python -c "import secrets; print(secrets.token_urlsafe())"'

```

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](https://hasura.io/community/)!