A Comprehensive Guide for Integrating Google Firebase Authentication with Django Rest Framework
This article will show you how to integrate Google Firebase authentication with Django REST Framework. We will use Firebase authentication to create and login users in Django and get firebase access token that will be use for each request to any protected route. We will also use Firebase to send custom email verification links to users.
Set up a virtual environment using the command: python3 -m venv venv
Activate the virtual environment: source venv/bin/activate
Install Django and Django Rest Framework: pip install django djangorestframework
create the project on your terminal: django-admin startproject django_with_firebase_auth .
Create an authentication app for the user management: django-admin startapp accounts
Add rest_framework and accounts to the INSTALLED_APPS in the django_with_firebase_auth/settings.py file.
Next, configure Firebase authentication:
Navigate to https://console.firebase.google.com/ and sign up.
Add a new project by providing a project name and clicking “Continue” to create it.
Access the Authentication section, select “Getting Started,” choose “Email and Password,” enable it, and save the configuration.
In the Project Overview, locate the project settings and click on it.
Scroll down to find the web app section (icon with iOS, Android, and </>). Click on </>, add your Firebase web app name, and register to save.
Continue to the console, scroll down to your app, and find the npm, CDN, and config options.
Click on “Config” and copy the credentials provided to your notepad.
In project settings, navigate to Service Accounts.
Choose Python and generate a new private key. Download the key in JSON format and copy its contents to your notepad.
click on authentication then click on settings then scroll down till you see authorized domains then add the name of your website to it which will be use to redirect user after they verify their email address or reset their password. yourwebsite.example
These steps complete the setup of Firebase authentication through the Google Console for your Django project.
add requirements.txt to your project root and then copy this dependencies to install into it then run pip install -r requirements.txt:
#requirements.txt
python-decouple
pyrebase4
firebase-admin
django-cors-headers
whitenoise
drf-yasg
create .env file in project root then add your credentials into it:
# firebase credentials
FIREBASE_API_KEY=”api-key”
FIREBASE_AUTH_DOMAIN=”auth-domain”
FIREBASE_STORAGE_BUCKET=”storage-bucket”
FIREBASE_DATABASE_URL=”none”
FIREBASE_ADMIN_SDK_CREDENTIALS_PATH=”path-to-your-firebase-admin-sdk-service-accounts.json”
# django secret key
SECRET_KEY =’secret-key’
# email credentials
EMAIL_HOST =’email-host’
EMAIL_PORT =port
EMAIL_HOST_USER =’email-host-user’
EMAIL_HOST_PASSWORD =’email-host-password’
go to your settings.py add the following to it:
import os
import pyrebase
from decouple import config as env_config
SECRET_KEY = env_config(“SECRET_KEY”)
# Firebase settings
try:
config = {
“apiKey”: os.getenv(“FIREBASE_API_KEY”),
“authDomain”: os.getenv(“FIREBASE_AUTH_DOMAIN”),
“databaseURL”: os.getenv(“FIREBASE_DATABASE_URL”),
“storageBucket”: os.getenv(“FIREBASE_STORAGE_BUCKET”),
}
firebase = pyrebase.initialize_app(config)
auth = firebase.auth()
except Exception:
raise Exception(“Firebase configuration credentials not found. Please add the configuration to the environment variables.”)
# custom user model
AUTH_USER_MODEL = ‘accounts.User’
# Django REST Framework settings
REST_FRAMEWORK = {
‘DEFAULT_AUTHENTICATION_CLASSES’: [
‘accounts.firebase_auth.firebase_authentication.FirebaseAuthentication’,
],
‘DEFAULT_PERMISSION_CLASSES’: [
‘rest_framework.permissions.IsAuthenticated’,
],
}
# authentication backend
AUTHENTICATION_BACKENDS = [
‘accounts.backends.model_backend.ModelBackend’,
]
# email settings
EMAIL_BACKEND = ‘django.core.mail.backends.smtp.EmailBackend’
EMAIL_HOST = env_config(“EMAIL_HOST”)
EMAIL_PORT = env_config(“EMAIL_PORT”)
EMAIL_USE_TLS = True
EMAIL_HOST_USER = env_config(“EMAIL_HOST_USER”)
EMAIL_HOST_PASSWORD = env_config(“EMAIL_HOST_PASSWORD”)
create a directory for firebase_auth then create a file for firebase exception and authentication:
# accounts/firebase_auth/firebase_exceptions.py
from rest_framework.exceptions import APIException
from rest_framework import status
class NoAuthToken(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = ‘No authentication token provided.’
default_code = ‘no_auth_token’
class InvalidAuthToken(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = ‘Invalid authentication token provided.’
default_code = ‘invalid_auth_token’
class FirebaseError(APIException):
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = ‘The user proivded with auth token is not a firebase user. it has no firebase uid.’
default_code = ‘no_firebase_uid’
class EmailVerification(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = ‘Email not verified.’
default_code = ‘email_not_verified’
# accounts/firebase_auth/firebase_authentication.py
from rest_framework import authentication
from .firebase_exceptions import NoAuthToken, InvalidAuthToken, FirebaseError, EmailVerification
from firebase_admin import auth, credentials
import firebase_admin
from accounts.models import User
import os
# Firebase Admin SDK credentials
try:
cred = credentials.Certificate(os.getenv(‘FIREBASE_ADMIN_SDK_CREDENTIALS_PATH’))
default_app = firebase_admin.initialize_app(cred)
except Exception:
raise FirebaseError(“Firebase Admin SDK credentials not found. Please add the path to the credentials file to the FIREBASE_ADMIN_SDK_CREDENTIALS_PATH environment variable.”)
class FirebaseAuthentication(authentication.BaseAuthentication):
keyword = ‘Bearer’
def authenticate(self, request):
auth_header = request.META.get(‘HTTP_AUTHORIZATION’)
if not auth_header:
raise NoAuthToken(“No authentication token provided.”)
id_token = auth_header.split(‘ ‘).pop()
decoded_token = None
try:
decoded_token = auth.verify_id_token(id_token)
except Exception:
raise InvalidAuthToken(“Invalid authentication token provided.”)
if not id_token or not decoded_token:
return None
email_verified = decoded_token.get(‘email_verified’)
if not email_verified:
raise EmailVerification(“Email not verified. please verify your email address.”)
try:
uid = decoded_token.get(‘uid’)
except Exception:
raise FirebaseError(“The user proivded with auth token is not a firebase user. it has no firebase uid.”)
try:
user = User.objects.get(firebase_uid=uid)
except User.DoesNotExist:
raise FirebaseError(“The user proivded with auth token is not a firebase user. it has no firebase uid.”)
return (user, None)
then create a utils directory and create custom email verification link:
# utils/custom_email_verification_link.py
from accounts.firebase_auth.firebase_authentication import auth as firebase_admin_auth
from django.core.mail import send_mail
from django.conf import settings
# create custom email verification link
def generate_custom_email_from_firebase(user_email, display_name):
action_code_settings = firebase_admin_auth.ActionCodeSettings(
url=’https://www.yourwebsite.example/',
handle_code_in_app=True,
)
custom_verification_link = firebase_admin_auth.generate_email_verification_link(user_email, action_code_settings)
subject = ‘Verify your email address’
message = f’Hello {display_name},\n\nPlease verify your email address by clicking on the link below:\n\n{custom_verification_link}\n\nThanks,\nYour website team’
send_email(subject, message, user_email)
# send email using django send_mail
def send_email(subject, message, user_email):
from_email = settings.EMAIL_HOST_USER
recipient = user_email
send_mail(subject, message, from_email, [recipient], fail_silently=False)
go to your accounts app then go to your models.py
#accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _
import uuid
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_(‘The Email must be set’))
email = self.normalize_email(email)
user = self.model(
email=email,
**extra_fields
)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault(‘is_staff’, True)
extra_fields.setdefault(‘is_superuser’, True)
if extra_fields.get(‘is_staff’) is False:
raise ValueError(_(‘Superuser must have is_staff=True.’))
if extra_fields.get(‘is_superuser’) is False:
raise ValueError(_(‘Superuser must have is_superuser=True.’))
return self.create_user(email, password, **extra_fields)
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(_(‘email address’), unique=True)
username = None
firebase_uid = models.CharField(max_length=255, blank=True, null=True)
USERNAME_FIELD = ‘email’
REQUIRED_FIELDS = []
objects = CustomUserManager()
def __str__(self):
return self.email
class Meta:
db_table = ‘user’
verbose_name = _(‘user’)
verbose_name_plural = _(‘users’)
ordering = [‘-date_joined’]
then create a new file serializers.py
#accounts/serializers.py
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = [‘id’, ‘firebase_uid’, ‘email’, ‘password’, ‘first_name’, ‘last_name’]
def create(self, validated_data):
password = validated_data.pop(‘password’, None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def update(self, instance, validated_data):
password = validated_data.pop(“password”, None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
if password is not None:
instance.set_password(password)
instance.save()
return instance
then go to your accounts/views.py to create signup and login user views:
#accounts/views.py
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from .models import User
from .serializers import UserSerializer
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework.permissions import AllowAny
from .firebase_auth.firebase_authentication import auth as firebase_admin_auth
from .utils.custom_email_verification_link import generate_custom_email_from_firebase
from django.contrib.auth.hashers import check_password
import re
from drf_with_firebase.settings import auth
class AuthCreateNewUserView(APIView):
permission_classes = [AllowAny]
authentication_classes = []
@swagger_auto_schema(
operation_summary=”Create a new user”,
operation_description=”Create a new user by providing the required fields.”,
tags=[“User Management”],
request_body=UserSerializer,
responses={201: UserSerializer(many=False), 400: “User creation failed.”}
)
def post(self, request, format=None):
data = request.data
email = data.get(‘email’)
password = data.get(‘password’)
first_name = data.get(‘first_name’)
last_name = data.get(‘last_name’)
included_fields = [email, password, first_name, last_name]
# Check if any of the required fields are missing
if not all(included_fields):
bad_response = {
“status”: “failed”,
“message”: “All fields are required.”
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
# Check if email is valid
if email and not re.match(r”[^@]+@[^@]+\.[^@]+”, email):
bad_response = {
“status”: “failed”,
“message”: “Enter a valid email address.”
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
# Check if password is less than 8 characters
if len(password) < 8:
bad_response = {
“status”: “failed”,
“message”: “Password must be at least 8 characters long.”
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
# Check if password contains at least one uppercase letter, one lowercase letter, one digit, and one special character
if password and not re.match(r’^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+{}\[\]:;<>,.?~\\-]).{8,}$’, password):
bad_response = {
“status”: “failed”,
“message”: “Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character.”}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
try:
# create user on firebase
user = auth.create_user_with_email_and_password(email, password)
# create user on django database
uid = user[‘localId’]
data[“firebase_uid”] = uid
data[“is_active”] = True
# sending custom email verification link
try:
user_email = email
display_name = first_name.capitalize()
generate_custom_email_from_firebase(user_email, display_name)
except Exception as e:
# delete user from firebase if email verification link could not be sent
firebase_admin_auth.delete_user(uid)
bad_response = {
“status”: “failed”,
“message”: str(e)
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
serializer = UserSerializer(data=data)
if serializer.is_valid():
serializer.save()
response = {
“status”: “success”,
“message”: “User created successfully.”,
“data”: serializer.data
}
return Response(response, status=status.HTTP_201_CREATED)
else:
auth.delete_user_account(user[‘idToken’])
bad_response = {
“status”: “failed”,
“message”: “User signup failed.”,
“data”: serializer.errors
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
except Exception:
bad_response = {
“status”: “failed”,
“message”: “User with this email already exists.”
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
class AuthLoginExisitingUserView(APIView):
permission_classes = [AllowAny]
authentication_classes = []
@swagger_auto_schema(
operation_summary=”Login an existing user”,
operation_description=”Login an existing user by providing the required fields.”,
tags=[“User Management”],
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
‘email’: openapi.Schema(type=openapi.TYPE_STRING, description=’Email of the user’),
‘password’: openapi.Schema(type=openapi.TYPE_STRING, description=’Password of the user’)
}
),
responses={200: UserSerializer(many=False), 404: “User does not exist.”}
)
def post(self, request: Request):
data = request.data
email = data.get(‘email’)
password = data.get(‘password’)
try:
user = auth.sign_in_with_email_and_password(email, password)
except Exception:
bad_response = {
“status”: “failed”,
“message”: “Invalid email or password.”
}
return Response(bad_response, status=status.HTTP_400_BAD_REQUEST)
try:
existing_user = User.objects.get(email=email)
# update password if it is not the same as the one in the database
if not check_password(password, existing_user.password):
existing_user.set_password(password)
existing_user.save()
serializer = UserSerializer(existing_user)
extra_data = {
“firebase_id”: user[‘localId’],
“firebase_access_token”: user[‘idToken’],
“firebase_refresh_token”: user[‘refreshToken’],
“firebase_expires_in”: user[‘expiresIn’],
“firebase_kind”: user[‘kind’],
“user_data”: serializer.data
}
response = {
“status”: “success”,
“message”: “User logged in successfully.”,
“data”: extra_data
}
return Response(response, status=status.HTTP_200_OK)
except User.DoesNotExist:
auth.delete_user_account(user[‘idToken’])
bad_response = {
“status”: “failed”,
“message”: “User does not exist.”
}
return Response(bad_response, status=status.HTTP_404_NOT_FOUND)
create a urls.py in your accounts directory:
# accounts/urls.py
from django.urls import path
from .views import (
AuthCreateNewUserView,
AuthLoginExisitingUserView,
)
urlpatterns = [
path(‘auth/sign-up/’, AuthCreateNewUserView.as_view(), name=’auth-create-user’),
path(‘auth/sign-in/’, AuthLoginExisitingUserView.as_view(), name=’auth-login-user’),
]
go to your project urls.py to register accounts.urls and also swagger url:
# django_with_firebase_auth/urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from rest_framework import permissions
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
schema_view = get_schema_view(
openapi.Info(
title=”Your title”,
default_version=’v1',
description=”your description”,
terms_of_service=”your terms of service”,
contact=openapi.Contact(email=”email”),
license=openapi.License(name=”license”),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
api_version = ‘v1’
urlpatterns = [
path(f’api/{api_version}/admin/’, admin.site.urls),
path(f’api/{api_version}/users/’, include(‘accounts.urls’)),
re_path(r’^swagger(?P<format>\.json|\.yaml)$’, schema_view.without_ui(cache_timeout=0), name=’schema-json’),
path(f’api/{api_version}/swagger/’, schema_view.with_ui(‘swagger’, cache_timeout=0), name=’schema-swagger-ui’),
path(f’api/{api_version}/redoc/’, schema_view.with_ui(‘redoc’, cache_timeout=0), name=’schema-redoc’),
]
then run makemigrations and migrate command :
python3 manage.py makemigrations
python3 manage.py migrate
export all the credential to python environment:
export FIREBASE_API_KEY=”api-key”
export FIREBASE_AUTH_DOMAIN=”auth-domain”
export FIREBASE_STORAGE_BUCKET=”storage-bucket”
export FIREBASE_DATABASE_URL=”none”
export FIREBASE_ADMIN_SDK_CREDENTIALS_PATH=”path-to-your-firebase-admin-sdk-service-accounts.json”
then start the server by running this command:
python3 manage.py runserver
go to your postman or swagger to test your signup and login endpoint. if works you will see your new user in firebase console and also stored in django database.
for the rest of the implementation like get user, update user, delete user, update email, reset password, change password, verify email, send password reset link, and verify password reset link. you can check the source code on github: https://github.com/Abiorh001/drf_with_firebase_auth