Boost Your Python Web App’s Security with Auth0 Integration

Siddharth Rakholiya
Simform Engineering
7 min readDec 5, 2023

Secure your Django REST Framework APIs with Auth0 and streamline user authentication and authorization.

Authentication and authorization are two important aspects of any web application. Authentication is the process of verifying the identity of a user, while authorization is the process of determining whether a user has the necessary permissions to access a particular resource.

Django is a popular Python web framework that provides a built-in authentication system. However, Django does not provide a built-in authorization system.

What is Auth0?

Auth0 is a cutting-edge platform that streamlines authentication, authorization, and user management services (RBAC), making it effortless for developers to elevate their web, mobile, and API-based applications without the hassle of starting from the ground up. It also provides pre-built SDKs and tools, eliminating the need for manual work and abstracting away the intricacies of authentication. Auth0 is a centralized and scalable solution and guarantees a seamless, secure, and user-friendly authentication experience.

Advantages:

  • With seamless integration and a variety of pre-built tools and SDKs, incorporating authentication into your applications has never been easier, thanks to Auth0.
  • Boasting scalability for projects of any size and prioritizing security with features like multi-factor authentication and threat intelligence, Auth0 has you covered on both ends.
  • Say goodbye to a disjointed authentication experience on different devices. Auth0’s Universal Login feature ensures an effortless and consistent process across all platforms and is fully customizable to fit your needs.
  • Don’t let complicated login processes discourage your users. Auth0’s Social Login Support allows for hassle-free sign-ins with existing credentials from popular social platforms.
  • Due to the dynamic scaling involved in microservices and serverless architectures, the scalability of Auth0 guarantees smooth and efficient authentication processes, allowing it to adapt seamlessly to fluctuating workloads.

Disadvantages:

  • One factor to keep in mind is the cost of using Auth0. For smaller projects or startups, Auth0’s pricing model may pose a disadvantage, as costs can increase with usage.
  • Implementing Auth0 may present a learning curve for developers who are new to the platform. This could potentially slow down the integration process and increase development time.
  • It’s also important to understand that using Auth0 means relying on an external service for authentication. This could be a problem if there are service outages or disruptions, leading to potential delays or security concerns.
  • In addition, the free tier of Auth0 has limitations, making it necessary for larger applications or businesses to opt for a paid plan, which adds to the overall cost. While customization is possible with Auth0, it may require a deep understanding of the platform, making it challenging for those with complex customization needs to implement effectively. Overall, careful consideration should be taken when deciding to use Auth0 and its potential impact on expenses and integration efficiency.

Create App, API, and enable RBAC in Auth0

Create an auth0 account: https://auth0.com/signup

Create an APP:

Once the Demo App has been created, go to the Settings -> Advanced Settings, and enable the below Grant Types:

Create API:

Enable RBAC settings in Auth0 Demo API:

Add permissions in Auth0 Demo API (APIs -> Auth0 Demo API -> Permissions):

Authorized Auth0 Demo API to access Auth0 management API:

Create a Management API token:

Create a role and add permissions (User Management -> Roles):

Save client ID, client Secret, and Domain (Applications -> Auth0 Demo APP -> Settings):

Integration with Django Rest Framework

Requirements:

python=3.8.10
Django=4.2.7
djangorestframework=3.14.0
auth0-python=4.5.0

Steps:

  • Create a virtual environment and activate it:
virtualenv  — python=’/usr/bin/python3.8' auth0-app-env
source auth0-app-env/bin/activate
  • Install the below dependencies:
pip install Django=4.2.7
pip install djangorestframework=3.14.0
pip install auth0-python=4.5.0
  • Create a Django project and application:
django-admin startproject auth0-demo
cd auth0-demo
python manage.py startapp users
  • Add environment variables or add them in settings.py:
AUTH0_DOMAIN="<auth0-domain>"
AUTH0_CLIENT_SECRET="<auth0-client-secrate>"
AUTH0_CLIENT_ID="<auth0-client-id>"
AUTH0_MMT_API_TOKEN="<auth0-management-api-token>"
  • Register users in Auth0 while signing up:
# views.py
# import below module along with DRF's module
from auth0.authentication import Database, GetToken
from auth0.exceptions import Auth0Error, RateLimitError
from auth0.management import Auth0


class SignUpView(GenericAPIView):
"""
post:
API for user signup
```
{
"first_name": "string",(required)
"last_name": "string",(required)
"email": "string",(required)
"password": "string",(required)
}
```
"""
permission_classes = (AllowAny,)
serializer_class = SignUpSerializer

def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
db = Database(settings.AUTH0_DOMAIN, settings.AUTH0_CLIENT_ID)
email = serializer.validated_data.pop("email")
password = serializer.validated_data.pop("password")
user_data = User.objects.create_user(email, password, **serializer.validated_data)

try:
# register user in auth0
resp = db.signup(
email=user_data.email,
password=password,
given_name=user_data.first_name,
family_name=user_data.last_name,
connection='Username-Password-Authentication'
)
logging.info(f"Auth0 API signup response => {resp}")
user_data.auth0_id = "auth0|" + resp["_id"]

# get roles from auth0
mgmt_auth = Auth0(settings.AUTH0_DOMAIN, settings.AUTH0_MMT_API_TOKEN)
role_id = mgmt_auth.roles.list()["roles"][0]["id"]
user_data.role_id = role_id
user_data.save()

# assign role to user in auth0
mgmt_auth.roles.add_users(role_id, users=[user_data.auth0_id])
return Response({"message": "user registered successful."},
status=status.HTTP_200_OK)
except (Auth0Error, RateLimitError) as e:
logging.info(f"Auth0 signup error resp => {str(e)}")
user_data.delete()
return Response(
{
"message": "something went wrong while creating user please try again.",
"error": str(e)
},
status=status.HTTP_400_BAD_REQUEST)
  • Authenticate user while login:
# views.py
class LoginAPIView(GenericAPIView):
permission_classes = (AllowAny,)
serializer_class = LoginAuthSerializer

def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
try:
user_check = User.objects.get(
email__iexact=serializer.validated_data['username'].lower().strip(),
is_delete=False, is_active=True, is_superuser=False)
except User.DoesNotExist:
response = \
{
"message": "Please enter correct username & password and try again",
}
return Response(response, status.HTTP_401_UNAUTHORIZED)
password = serializer.validated_data['password']

# verify user's credentials using auth0
auth = GetToken(
settings.AUTH0_DOMAIN,
settings.AUTH0_CLIENT_ID,
client_secret=settings.AUTH0_CLIENT_SECRET
)
try:
# login using auth0
resp = auth.login(
username=serializer.validated_data['username'].lower().strip(),
password=password, realm='Username-Password-Authentication'
)
logging.info(f"Auth0 login API resp => {resp}")
except (Auth0Error, RateLimitError) as e:
logging.info(f"Auth0 login API error resp => {str(e)}")
return Response(
{
"message": "Please enter valid username & password and try again",
"error": str(e)
},
status.HTTP_401_UNAUTHORIZED)

user = authenticate(username=user_check.email, password=password)
if user and user.is_authenticated:
login(request, user)
return Response({"message": "User logged-in successfully",
"user": user.id,
"access_token": resp["access_token"],
"expires_in": resp["expires_in"],
"token_type": resp["token_type"],
},
status=status.HTTP_200_OK)
return Response({"message": "Please enter valid username & password and try again"},
status.HTTP_401_UNAUTHORIZED)
else:
return Response(serializer.error_messages,
status=status.HTTP_400_BAD_REQUEST)
  • Create middleware, and apply auth0 and RBCA to the rest of the APIs:
from auth0.authentication import Users
from auth0.exceptions import Auth0Error, RateLimitError
from auth0.management import Auth0
from django.conf import settings
from django.http import JsonResponse

from users.models import User


class Auth0Middleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
if request.path in ["/users/login/", "/users/signup/"]:
return self.get_response(request)
auth_token = request.META.get("HTTP_AUTHORIZATION")
if not auth_token:
response_data = {'error': 'Unauthorized', 'message': 'Invalid token'}
return JsonResponse(response_data, status=401)
user = Users(domain=settings.AUTH0_DOMAIN)

try:
# get user information from au
user_info = user.userinfo(access_token=request.META["HTTP_AUTHORIZATION"])
request.user = User.objects.get(username=user_info["email"])

# get user's permissions from auth0
mgmt_auth = Auth0(settings.AUTH0_DOMAIN, settings.AUTH0_MMT_API_TOKEN)
permissions = mgmt_auth.roles.list_permissions(request.user.role_id)
user_permissions = [
permission["permission_name"] for permission in permissions["permissions"]
]
request.user.auth0_permission = user_permissions
except (Auth0Error, RateLimitError) as e:
return JsonResponse({'error': str(e)}, status=401)
return self.get_response(request)

# add this middleware in settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'users.middleware.Auth0Middleware', # Auth0 middleware
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • Create an API and allocate access based on the permissions defined in Auth0:
# views.py
class UserViewSet(viewsets.ModelViewSet):
permission_classes = (UserPermission,)

def get_queryset(self):
user_id = self.request.user.id
return User.objects.filter(id=user_id,
is_delete=False)

def get_serializer_class(self):
if self.action == "partial_update":
return UserUpdateSerializer
else:
return UserBasicSerializer

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(
instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)

# permissions.py
from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
if "read:users" in request.user.auth0_permission and \
view.action in ["list", "retrieve"]:
return True
elif "delete:users" in request.user.auth0_permission and \
view.action == "destroy":
return True
elif "update:users" in request.user.auth0_permission and \
view.action == "partial_update":
return True
else:
return False
return False

def has_object_permission(self, request, view, obj):
return obj == request.user
  • Add token responded by login API Authorization variable:
  • To create a new management token:
from auth0.authentication import GetToken
from django.conf import settings

def get_mgmt_token():
get_token = GetToken(domain=settings.AUTH0_DOMAIN,
client_id=settings.AUTH0_CLIENT_ID,
client_secret=settings.AUTH0_CLIENT_SECRET)
token = get_token.client_credentials(
'https://{}/api/v2/'.format(settings.AUTH0_DOMAIN)
)
return token['access_token']

References:

Conclusion

By adopting this approach, you’ve not only secured your API but also established a robust mechanism for effortless user management and access control, ensuring your application remains secure and scalable as it evolves.

For more updates on the latest development trends, follow the Simform Engineering blog.

Follow Us: Twitter | LinkedIn

--

--