I've built a Django API that uses django-graphql-auth and django-graphql-jwt packages to implement authentication. I followed the package's documentation and got everything to work and everything is working from my Angular UI. The only issue is that even requests made from Postman without the Authorization header, are able to fetch the data from the graphql API.
This is my Django project's settings.py
"""
Django settings for myproject project.
Generated by 'django-admin startproject' using Django 3.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from pathlib import Path
import os
import sys
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-)3@2sm6lgn_p83_t(l-44hd16ou5-qbk=rso!$b1#$fu*n2^rq'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
CORS_ORIGIN_ALLOW_ALL = True
# Application definition
INSTALLED_APPS = [
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
'graphene_django',
'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
'graphql_auth',
'rest_framework',
'django_filters'
]
GRAPHENE = {
'SCHEMA': 'myproject.schema.schema',
'MIDDLEWARE': [
'graphql_jwt.middleware.JSONWebTokenMiddleware',
],
}
GRAPHENE_DJANGO_EXTRAS = {
'DEFAULT_PAGINATION_CLASS': 'graphene_django_extras.paginations.LimitOffsetGraphqlPagination',
'DEFAULT_PAGE_SIZE': 20,
'MAX_PAGE_SIZE': 50,
'CACHE_ACTIVE': True,
'CACHE_TIMEOUT': 300 # seconds
}
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.utils.UpdateLastActivityMiddleware'
]
AUTHENTICATION_BACKENDS = [
'graphql_auth.backends.GraphQLAuthBackend',
'django.contrib.auth.backends.ModelBackend',
]
GRAPHQL_JWT = {
"JWT_ALLOW_ANY_CLASSES": [
"graphql_auth.mutations.Register",
"graphql_auth.mutations.VerifyAccount",
"graphql_auth.mutations.ResendActivationEmail",
"graphql_auth.mutations.SendPasswordResetEmail",
"graphql_auth.mutations.PasswordReset",
"graphql_auth.mutations.ObtainJSONWebToken",
"graphql_auth.mutations.VerifyToken",
"graphql_auth.mutations.RefreshToken",
"graphql_auth.mutations.RevokeToken",
],
'JWT_PAYLOAD_HANDLER': 'common.utils.jwt_payload',
"JWT_VERIFY_EXPIRATION": True,
"JWT_LONG_RUNNING_REFRESH_TOKEN": True
}
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myprojectdb',
'USER': 'myprojectadmin',
'PASSWORD': 'password',
'HOST': 'db',
'PORT': '5432',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = (
BASE_DIR / "static",
'/var/www/static/',
)
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# This is here because we are using a custom User model
# https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = "myapp.User"
urls.py
from django.contrib import admin
from django.urls import include, path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', include('myapp.urls')),
path('admin/', admin.site.urls),
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Sample query:-
query users {
users {
id
nickName
lastLogin
}
}
As you can see, I've followed all the necessary steps to accomodate the instructions in both the packages. How do I prevent unauthorized requests accessing my data?
Updates:-
I'm using Django-graphql-extras for pagination, filtering on my Graphql api. So the queries are all using the built-in methods from that package.
The mutations however are manual.
Queries file:-
from graphene_django.types import ObjectType
from .gqTypes import InstitutionType, UserType, GroupType
from graphene_django_extras import DjangoObjectField, DjangoFilterPaginateListField, LimitOffsetGraphqlPagination
class Query(ObjectType):
institution = DjangoObjectField(
InstitutionType, description='Single User query')
user = DjangoObjectField(UserType, description='Single User query')
group = DjangoObjectField(GroupType, description='Single User query')
institutions = DjangoFilterPaginateListField(
InstitutionType, pagination=LimitOffsetGraphqlPagination())
users = DjangoFilterPaginateListField(
UserType, pagination=LimitOffsetGraphqlPagination())
groups = DjangoFilterPaginateListField(
GroupType, pagination=LimitOffsetGraphqlPagination())
Sample mutation code:-
class CreateUser(graphene.Mutation):
class Meta:
description = "Mutation to create a new User"
class Arguments:
input = UserInput(required=True)
ok = graphene.Boolean()
user = graphene.Field(UserType)
@staticmethod
def mutate(root, info, input=None):
ok = True
error = ""
if input.name is None:
error += "Name is a required field<br />"
if len(error) > 0:
raise GraphQLError(error)
searchField = input.name
searchField += input.title if input.title is not None else ""
searchField += input.bio if input.bio is not None else ""
searchField = searchField.lower()
user_instance = User(user_id=input.user_id, title=input.title, bio=input.bio,
institution_id=input.institution_id, searchField=searchField)
user_instance.save()
return CreateUser(ok=ok, user=user_instance)
You should add the login_required
decorator to your queries and mutations resolvers. Like this:
from graphql_jwt.decorators import login_required
class Query(graphene.ObjectType):
viewer = graphene.Field(UserType)
@login_required
def resolve_viewer(self, info, **kwargs):
return info.context.user
In your case put it after staticmethod decorator, like this:
@staticmethod
@login_required
def mutate():
pass