User authorization is a vital part of any business application. Every developer faces the authorization challenge. There are many ways to implement authorization in Django. Django has a built-in permission-based authorization system, and there are some third-party apps like django-guardian
and django-rules
.
In our previous article, we implemented Token-based authentication with django-graphql-jwt
. It has also some useful decorators to implement authorization.
But in this article, I will share a custom way to implement role-based authorization in GraphQL. Maybe in the next article, I will share some other ways of authorizations with Django’s built-in permission module.
Let’s start writing some code.
Basic Setup
At first, go to account/models.py
file and add the following code.
from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): class Meta: db_table = "user" username = models.CharField( max_length=64, unique=True ) role = models.ForeignKey( 'account.Role', on_delete=models.CASCADE, blank=True, null=True ) def __str__(self): return self.username class Right(models.Model): class Meta: db_table = "right" ordering = ["name"] name = models.CharField(max_length=64) codename = models.CharField(max_length=64) description = models.TextField(blank=True, null=True) def __str__(self): return self.name class Role(models.Model): class Meta: db_table = "role" name = models.CharField(max_length=64) description = models.TextField(blank=True, null=True) rights = models.ManyToManyField(Right) def __str__(self): return self.name
Here we are extending AbstractUser
from Django’s auth module. And added the other two models for Role
and Right
. Nothing fancy here. Everything is straightforward.
Now go to settings.py
and add the following code here.
AUTH_USER_MODEL = 'account.User'
Now run the following commands to generate migrations.
python manage.py makemigrations python manage.py migrate
Ok. Cool! let’s write some schema for role and rights and make some other changes in mutation.
Writing Schema
Now go to account/schema/users.py
file and add the following schema.
import graphene import graphql_jwt from graphene_django.types import DjangoObjectType, ObjectType from account.models import User, Role, Right class UserFields: id = graphene.ID() username = graphene.String(required=True) password = graphene.String(required=True) email = graphene.String(required=True) class RightFields: id = graphene.ID() name = graphene.String() codename = graphene.String() description = graphene.String() class RoleFiels: id = graphene.ID() name = graphene.String() description = graphene.String() class RightType(DjangoObjectType, RightFields): class Meta: model = Right class RoleType(DjangoObjectType, RoleFiels): class Meta: model = Role rights = graphene.List(RightType) class UserType(DjangoObjectType, UserFields): class Meta: model = User class RightInputType(graphene.InputObjectType, RightFields): pass class RoleInputType(graphene.InputObjectType, RoleFiels): rights = graphene.List(RightInputType) class UserInputType(graphene.InputObjectType, UserFields): role = graphene.Field(RoleInputType)
We don’t need to make any change to our Query. But CreateUser
mutation needs some changes. So, update it with the following code.
class CreateUser(graphene.Mutation): class Arguments: input = UserInputType(required=True) ok = graphene.Boolean() user = graphene.Field(UserType) @staticmethod def mutate(root, info, input): rights = input.role.rights right_ids = [] for right in rights: obj, created = Right.objects.get_or_create(name=right.name, codename=right.codename) right_ids.append(obj.id) role, created = Role.objects.get_or_create(name=input.role.name, description=input.role.description) role.rights.set(right_ids) user = User() for key, val in input.items(): if key is "role": val = role setattr(user, key, val) user.set_password(input.password) user.save() return CreateUser(ok=True, user=user)
Role and Permission Decorator
We will write a custom decorator to check the current user role and permissions. So, add a utility function to blog/utils.py
file. Later, we will add a common app and move utility functions here. At the top, import GraphQLError
like this
from graphql import GraphQLError
And add the following decorator function.
def can(*permissions): def wrapped_decorator(func): def inner(cls, info, *args, **kwargs): if not info.context: raise GraphQLError("Permission Denied.") user = info.context.user if not user.is_authenticated or not user.role: raise GraphQLError("Permission Denied.") # An admin (Django superusers) can do everything. if user.is_superuser: return func(cls, info, **kwargs) # A user CAN perform an action, if he has ANY of the requested permissions. user_permissions = list( user.role.rights.all().values_list("codename", flat=True) ) if any(permission in user_permissions for permission in permissions): return func(cls, info, **kwargs) raise GraphQLError("Permission Denied.") return inner return wrapped_decorator
Use Permission Decorator
It’s time to use the above decorator function. Go to `account/schema/users.py` and import the decorator function as following.
from blog.utils import can
And use the function as a decorator like this
class Query(ObjectType): me = graphene.Field(UserType) users = graphene.List(UserType) @can("manage_own_profile") def resolve_me(self, info, **kwargs): user = info.context.user if user.is_anonymous: raise Exception('Please login!') return user def resolve_users(self, info, **kwargs): return User.objects.all()
Test role and permission
Open graphiql
in your browser and add the following mutation.
mutation createUser { createUser(input: { username: "ijhar_admin", email: "ijharislam@gmail.com", password: "morning_blog" role: { name: "admin", description: "Person with Admin role will have all the access to the application", rights:[ { name: "Manage Own Profile", codename: "manage_own_profile" }, { name: "Manage Blog", codename: "manage_blog" }, { name: "Manage Authors", codename: "manage_authors" } ] } }) { ok user { id username email role { name } } } }
It will return the following result.

To get the JWT token, add the following mutation.
mutation { tokenAuth(username:"ijhar_admin", password: "morning_blog") { token } }
Here we got this result.
{ "data": { "tokenAuth": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImlqaGFyX2FkbWluIiwiZXhwIjoxNTc5MzI3OTY4LCJvcmlnSWF0IjoxNTc5MzI3NjY4fQ.fvRq8GGfU_DZkj5QywrbASAILYIT52VBt-WYFin4xuU" } } }
Now we have to use this token to access any particular information. For example, we added @can("manage_own_profile")
decorator to me
query. If we want to access the current user’s info from it, it will give following error.

This error message is coming from our permission decorator.
To access this information, we need to pass Authorization
header with a valid JWT token for a user who has manage_own_profile
permission.
Graphiql
does not have support to add a header with the query. So, to test the permission, we will use Postman
. Recently postman added GraphQL to their console which is in beta version. So, open postman and add a header like this.

It worked! Awesome. Here we have to add the token with JWT
prefix, not with Bearer
.
Let’ s create another user, who will not have manage_own_profile
permission. So, add the following code.
mutation createUser { createUser(input: { username: "ijhar_staff", email: "ijharislam@gmail.com", password: "morning_blog" role: { name: "staff", description: "Person with Admin role will have all the access to the application", rights:[ { name: "Manage Blog", codename: "manage_blog" }, { name: "Manage Authors", codename: "manage_authors" } ] } }) { ok user { id username email role { name } } } }
You will get the following result.

Let get the JWT token for this user.

I got this token here.
{ "data": { "tokenAuth": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImlqaGFyX3N0YWZmIiwiZXhwIjoxNTc5MzI5MzkzLCJvcmlnSWF0IjoxNTc5MzI5MDkzfQ.L9mb4cKX_AXw9upFVhr650xhq2VxKeHgfltXYSa_S70" } } }
I will use this token in Postman for the above query. This time, I got the following result.

Cool! It worked then. The ijhar_admin
user has the right permission, so, it gave access to the information but the second user ijhar_staff
does not have the permission. So, It didn’t give access.
So, our decorator is working perfectly. We can check more than one permission with it like this.
@can("manage_own_profile", "manage_authors_profile")
If you would like to check out the full source code, check it out from my repo.
Full-stack Developer (Python | Django | React | React-Native | Angular | Vue)
Leave a Reply