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.userand 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:
- Django's
AuthenticationMiddlewareruns first FirebaseAuthenticationMiddlewareauthenticates via FirebaseTenantMiddlewaresets 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
- 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);
- Make authenticated API requests:
const response = await fetch('https://api.coditect.com/api/v1/users/', {
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json',
},
});
- 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 token403 Forbidden- User has no organization500 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
- 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
}'
- Test authenticated endpoint:
curl -X GET 'http://localhost:8000/api/v1/users/' \
-H 'Authorization: Bearer YOUR_FIREBASE_TOKEN'
- 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
-
Authentication Failures:
- Invalid tokens
- Expired tokens
- User not found
-
Performance:
- Token verification latency
- User lookup time
-
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:
- Check GCP credentials:
gcloud auth application-default login - Verify Workload Identity binding (GKE)
- 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:
- Update client to use Firebase tokens instead of Django JWT
- 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
],
}
- Monitor Firebase token usage vs. Django JWT
- Remove Django JWT when migration complete
References
- Firebase Admin SDK Documentation
- Firebase Authentication
- Django Middleware
- django-multitenant
- GCP Workload Identity
Support
For issues or questions:
- Documentation: This guide
- Code:
api/middleware/firebase_auth.py - Tests:
api/tests/test_firebase_auth_middleware.py - Repository: https://github.com/coditect-ai/coditect-cloud-backend