Skip to main content

Firebase JWT Authentication Integration Guide

Overview

This document provides comprehensive guidance for integrating Firebase JWT authentication into the CODITECT Cloud Backend Django application.

Architecture

Client (Web/Mobile)
|
| 1. Login with Firebase Auth
v
Firebase Authentication
|
| 2. Issues JWT token
v
Client sends API request
|
| 3. Authorization: Bearer <firebase-token>
v
Django API (FirebaseAuthenticationMiddleware)
|
| 4. Verify token with Firebase Admin SDK
v
| 5. Lookup user by firebase_uid
v
User Model
|
| 6. Set request.user and tenant context
v
TenantMiddleware
|
| 7. Process request with tenant-scoped queries
v
View/API Endpoint

Components

1. FirebaseAuthenticationMiddleware

Location: api/middleware/firebase_auth.py

Responsibilities:

  • Extract Firebase JWT token from Authorization header
  • Verify token using Firebase Admin SDK
  • Look up user by Firebase UID
  • Set request.user and tenant context
  • Handle authentication errors gracefully

Public Endpoints (no auth required):

  • /health/*
  • /admin/*
  • /api/v1/auth/*
  • /api/schema/*
  • /api/docs/*
  • /static/*
  • /media/*

2. User Model Enhancement

Location: users/models.py

The User model includes a firebase_uid field:

class User(AbstractUser, TenantModel):
firebase_uid = models.CharField(max_length=255, unique=True, null=True, blank=True)
# ... other fields

This field must be populated when users are created via Firebase authentication.

3. Middleware Configuration

Location: license_platform/settings/base.py

The middleware is configured in the MIDDLEWARE list:

MIDDLEWARE = [
# ... other middleware
'django.contrib.auth.middleware.AuthenticationMiddleware',
'api.middleware.FirebaseAuthenticationMiddleware', # Firebase JWT
'tenants.middleware.TenantMiddleware', # Tenant context
# ... other middleware
]

Order is critical:

  1. Django's AuthenticationMiddleware runs first
  2. FirebaseAuthenticationMiddleware authenticates via Firebase
  3. TenantMiddleware sets tenant context from authenticated user

Setup

1. Install Dependencies

pip install firebase-admin==6.3.0

This is already included in requirements.txt.

2. Firebase Admin SDK Initialization

The middleware initializes Firebase Admin SDK automatically using Google Cloud Workload Identity:

# In api/middleware/firebase_auth.py
if not firebase_admin._apps:
firebase_admin.initialize_app()

No service account key required when running in GCP with Workload Identity enabled.

3. GCP Workload Identity Configuration

For local development, set up Application Default Credentials:

gcloud auth application-default login

For production (GKE):

# kubernetes/deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: coditect-backend
annotations:
iam.gke.io/gcp-service-account: coditect-backend@PROJECT_ID.iam.gserviceaccount.com
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
serviceAccountName: coditect-backend

4. Create IAM Binding

gcloud iam service-accounts add-iam-policy-binding \
coditect-backend@PROJECT_ID.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:PROJECT_ID.svc.id.goog[default/coditect-backend]"

Usage

Client-Side Authentication Flow

  1. User logs in with Firebase:
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const auth = getAuth();
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const idToken = await userCredential.user.getIdToken();

// Store token for API requests
localStorage.setItem('firebase_token', idToken);
  1. Make authenticated API requests:
const response = await fetch('https://api.coditect.com/api/v1/users/', {
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json',
},
});
  1. Refresh token when expired:
const auth = getAuth();
const user = auth.currentUser;
const freshToken = await user.getIdToken(true); // Force refresh

Server-Side User Creation

When a user signs up via Firebase, create the corresponding Django user:

from users.models import User
from tenants.models import Organization

# After Firebase user creation
firebase_uid = "firebase-uid-from-auth"
email = "user@example.com"

# Create organization (if new user)
organization = Organization.objects.create(name="User's Organization")

# Create Django user
user = User.objects.create_user(
email=email,
firebase_uid=firebase_uid,
organization=organization,
role='owner' # First user in organization
)

Error Handling

Client-Side Error Handling

async function makeAuthenticatedRequest(url, options = {}) {
const token = localStorage.getItem('firebase_token');

const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${token}`,
...options.headers,
},
});

if (response.status === 401) {
// Token expired or invalid - refresh and retry
const auth = getAuth();
const user = auth.currentUser;
const freshToken = await user.getIdToken(true);
localStorage.setItem('firebase_token', freshToken);

// Retry request with fresh token
return fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${freshToken}`,
...options.headers,
},
});
}

return response;
}

Error Response Format

All authentication errors return JSON:

{
"error": "authentication_failed",
"detail": "Invalid or expired Firebase token"
}

Status codes:

  • 401 Unauthorized - Missing, invalid, or expired token
  • 403 Forbidden - User has no organization
  • 500 Internal Server Error - Server-side error (logged)

Testing

Running Unit Tests

# Run all middleware tests
pytest api/tests/test_firebase_auth_middleware.py -v

# Run with coverage
pytest api/tests/test_firebase_auth_middleware.py --cov=api.middleware --cov-report=html

Manual Testing with curl

  1. Get Firebase token:
# Using Firebase REST API
curl -X POST 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "test@example.com",
"password": "password123",
"returnSecureToken": true
}'
  1. Test authenticated endpoint:
curl -X GET 'http://localhost:8000/api/v1/users/' \
-H 'Authorization: Bearer YOUR_FIREBASE_TOKEN'
  1. Test public endpoint:
curl -X GET 'http://localhost:8000/api/v1/health/'
# No Authorization header required

Integration Testing

import pytest
from django.test import Client
from unittest.mock import patch

@pytest.mark.django_db
def test_authenticated_api_request():
"""Test that authenticated requests work end-to-end."""
client = Client()

# Mock Firebase token verification
with patch('firebase_admin.auth.verify_id_token') as mock_verify:
mock_verify.return_value = {'uid': 'firebase-uid-123'}

# Create user with firebase_uid
user = User.objects.create_user(
email='test@example.com',
firebase_uid='firebase-uid-123',
organization=organization
)

# Make authenticated request
response = client.get(
'/api/v1/users/',
HTTP_AUTHORIZATION='Bearer mock-token'
)

assert response.status_code == 200

Monitoring

Logging

The middleware logs authentication events:

import logging
logger = logging.getLogger(__name__)

# Success
logger.debug(f"Authenticated user {user.email} for tenant {org.name}")

# Errors
logger.warning(f"No user found with Firebase UID: {firebase_uid}")
logger.error(f"Failed to set tenant context: {e}", exc_info=True)

Metrics to Monitor

  1. Authentication Failures:

    • Invalid tokens
    • Expired tokens
    • User not found
  2. Performance:

    • Token verification latency
    • User lookup time
  3. Errors:

    • Tenant context setting failures
    • Firebase Admin SDK errors

Security Considerations

Token Expiration

Firebase ID tokens expire after 1 hour. Clients must refresh tokens:

setInterval(async () => {
const auth = getAuth();
const user = auth.currentUser;
if (user) {
const freshToken = await user.getIdToken(true);
localStorage.setItem('firebase_token', freshToken);
}
}, 55 * 60 * 1000); // Refresh every 55 minutes

Token Validation

The middleware validates:

  • Token signature (via Firebase Admin SDK)
  • Token expiration
  • Token revocation status
  • UID claim presence

User Authorization

After authentication, authorization is handled by:

  • Django REST Framework permissions
  • Tenant isolation (django-multitenant)
  • Role-based access control (User.role field)

Troubleshooting

"Firebase Admin SDK not initialized"

Cause: Firebase Admin SDK initialization failed.

Solution:

  1. Check GCP credentials: gcloud auth application-default login
  2. Verify Workload Identity binding (GKE)
  3. Check logs for initialization errors

"User not found with Firebase UID"

Cause: User exists in Firebase but not in Django database.

Solution: Create Django user when Firebase user signs up:

# In user registration endpoint
firebase_uid = decoded_token['uid']
user = User.objects.create_user(
email=email,
firebase_uid=firebase_uid,
organization=organization
)

"User must belong to an organization"

Cause: User has no associated organization.

Solution: Ensure all users are assigned to an organization:

# Fix existing users
users_without_org = User.objects.filter(organization__isnull=True)
for user in users_without_org:
org = Organization.objects.create(name=f"{user.email}'s Organization")
user.organization = org
user.save()

"Invalid or expired Firebase token"

Cause: Token expired (>1 hour old) or invalid.

Solution: Client must refresh token:

const freshToken = await user.getIdToken(true);

Migration from JWT to Firebase

If migrating from rest_framework_simplejwt:

  1. Update client to use Firebase tokens instead of Django JWT
  2. Keep both middlewares during transition period:
MIDDLEWARE = [
# ... other middleware
'api.middleware.FirebaseAuthenticationMiddleware', # New
'tenants.middleware.TenantMiddleware',
]

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication', # Old (fallback)
# FirebaseAuthenticationMiddleware handles Firebase tokens
],
}
  1. Monitor Firebase token usage vs. Django JWT
  2. Remove Django JWT when migration complete

References

Support

For issues or questions: