Firebase Authentication in Django REST Framework
↳ Django
19 Jan 2020 | 11 minute read
Table of Contents
In this article, you will learn how to use Firebase as your identity provider for your Django REST API using Django REST Framework.
Authentication is one of those things that is easy to implement but takes quite some effort to perfect. As of this, I often use external services for authentication, such as Auth0 or Firebase. Using an IDaaS
enables you to focus on other parts of your application, knowing that the authentication game is in good hands.
For example, Auth0 and Firebase deal with authentication not only using username and password, but also social login, mobile login, and other authentication options, and they both offer SDKs which enables a smooth integration process.
I'm currently working on a React Native application, where I'm using Django and Django REST Framework on the backend. As I'm using Firebase in the mobile app for Remote Config, I decided to also use Firebase for authentication.
Let's have a look at how to implement Firebase Authentication for your Django REST API.
Create a Firebase Authentication application inside of your project
To structure your code as reusable components, create a new application inside of your project. I've named mine firebase_auth
, and this is where all code related to Firebase authentication will live.
Load your application in settings.py
We need to include the firebase_auth
application in our Django project. To do so, add it to the INSTALLED_APPS
list.
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'REST_framework',
'corsheaders',
'firebase_auth',
]
Create a firebase authentication class
To use Firebase for authentication in our REST API, we need to create an authentication class
inheriting authentication.BaseAuthentication
that can be used by Django REST Framework.
Let's start by creating the file authentication.py
inside of the firebase_auth
application. To use Firebase for authentication, we need to initialise a firebase application using our credentials received from the Firebase admin dashboard. To safely store my firebase credentials, I'm storing these as environment variables.
authentication.py
import os
import firebase_admin
from firebase_admin import credentials
cred = credentials.Certificate({
"type": "service_account",
"project_id": os.environ.get('FIREBASE_PROJECT_ID'),
"private_key_id": os.environ.get('FIREBASE_PRIVATE_KEY_ID'),
"private_key": os.environ.get('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'),
"client_email": os.environ.get('FIREBASE_CLIENT_EMAIL'),
"client_id": os.environ.get('FIREBASE_CLIENT_ID'),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ.get('FIREBASE_CLIENT_CERT_URL')
})
default_app = firebase_admin.initialize_app(cred)
Our next step is to create the FirebaseAuthentication
class. In the class, we need to declare an authenticate
method which accepts the request
and returns the user
. There are a few steps you need to take to validate and identify the user from the authentication token.
- Ensure that an authentication token is present.
- Verify the token using the
verify_id_token
function. - Get (or create, depending on your preferences) the user.
- [Optional] Update the user's last activity.
- Return the user.
authentication.py
class FirebaseAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.META.get("HTTP_AUTHORIZATION")
if not auth_header:
raise NoAuthToken("No auth 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 auth token")
if not id_token or not decoded_token:
return None
try:
uid = decoded_token.get("uid")
except Exception:
raise FirebaseError()
user, created = User.objects.get_or_create(username=uid)
user.profile.last_activity = timezone.localtime()
return (user, None)
To raise exceptions with more context, I've created a few custom exceptions in exceptions.py
inside the firebase_auth
folder used in the FirebaseAuthentication
class.
exceptions.py
from REST_framework import status
from REST_framework.exceptions import APIException
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_token"
class FirebaseError(APIException):
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = "The user provided with the auth token is not a valid Firebase user, it has no Firebase UID"
default_code = "no_firebase_uid"
The final authentication.py
file looks like this:
authentication.py
import os
import firebase_admin
from django.conf import settings
from django.contrib.auth.models import User
from django.utils import timezone
from firebase_admin import auth
from firebase_admin import credentials
from REST_framework import authentication
from REST_framework import exceptions
from .exceptions import FirebaseError
from .exceptions import InvalidAuthToken
from .exceptions import NoAuthToken
cred = credentials.Certificate(
{
"type": "service_account",
"project_id": os.environ.get("FIREBASE_PROJECT_ID"),
"private_key_id": os.environ.get("FIREBASE_PRIVATE_KEY_ID"),
"private_key": os.environ.get("FIREBASE_PRIVATE_KEY").replace("\\n", "\n"),
"client_email": os.environ.get("FIREBASE_CLIENT_EMAIL"),
"client_id": os.environ.get("FIREBASE_CLIENT_ID"),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ.get("FIREBASE_CLIENT_CERT_URL"),
}
)
default_app = firebase_admin.initialize_app(cred)
class FirebaseAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.META.get("HTTP_AUTHORIZATION")
if not auth_header:
raise NoAuthToken("No auth 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 auth token")
pass
if not id_token or not decoded_token:
return None
try:
uid = decoded_token.get("uid")
except Exception:
raise FirebaseError()
user, created = User.objects.get_or_create(username=uid)
user.profile.last_activity = timezone.localtime()
return (user, None)
Update default authentication classes
To use our FirebaseAuthentication
class as an authentication class, we need to let Django know that it exists by adding it to DEFAULT_AUTHENTICATION_CLASSES
.
I've decided to keep the SessionAuthentication
authentication class provided by Django REST Framework to be able to explore use the Django REST Framework API explorer without a firebase token, and therefore added the FirebaseAuthentication
below the SessionAuthentication
in DEFAULT_AUTHENTICATION_CLASSES
.
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'REST_framework.authentication.SessionAuthentication',
'firebase_auth.authentication.FirebaseAuthentication',
),
}
That's it! Now you're all set up to use Firebase as your authentication provider in your Django project.