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.