ngo Configuration
Configure settings.py to enable the authentication stack. Key decisions include enabling JWT mode in dj-rest-auth, defining the Google scopes, and configuring CORS for the React frontend.
# platform_api/settings.py
INSTALLED_APPS = [
# ... default apps
'django.contrib.sites',
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'dj_rest_auth',
'dj_rest_auth.registration',
'identity',
]
# Mandatory for allauth social providers
SITE_ID = 1
# REST Framework Configuration
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
# dj-rest-auth JWT Settings
REST_AUTH = {
'USE_JWT': True,
'JWT_AUTH_COOKIE': 'platform-auth',
'JWT_AUTH_HTTPONLY': False, # Allow JS access if storing in localStorage; True for cookies
}
# Authentication Backends
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
# Google Provider Configuration
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
],
'AUTH_PARAMS': {
'access_type': 'online',
}
}
}
# CORS Configuration for React SPA
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173",
]
3. Google Cloud Identity Provisioning
Before implementing the API, provision the credentials in the Google Cloud Console.
- Create Project: Navigate to the Google Cloud Console and create a new project.
- OAuth Consent Screen: Configure the consent screen as "External". Add required app information. Skip scopes and test users for now; these can be refined later.
- Credentials:
- Create credentials of type OAuth Client ID.
- Application type: Web application.
- Authorized JavaScript Origins: Add
http://localhost:5173. This restricts browser-based requests to your frontend domain.
- Authorized Redirect URIs: Add
http://localhost:5173/auth/callback. This is the redirect target after user consent.
- Save and record the Client ID and Client Secret.
4. Token Exchange Endpoint
Create the view that accepts the Google token and returns a JWT. This view leverages dj-rest-auth's SocialLoginView, which handles the validation and user mapping.
# identity/views.py
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
class GoogleTokenExchangeView(SocialLoginView):
"""
Endpoint to exchange a Google OAuth token for a platform JWT.
"""
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
Map the view to a URL route.
# identity/urls.py
from django.urls import path
from .views import GoogleTokenExchangeView
urlpatterns = [
path('v1/auth/social/google/', GoogleTokenExchangeView.as_view(), name='google_token_exchange'),
]
Include this in the project-level urls.py.
# platform_api/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('identity.urls')),
]
5. React Frontend Integration
On the frontend, use @react-oauth/google to handle the user consent flow. The strategy is to capture the access token and exchange it via the Django API.
Service Layer: Abstract the API call to keep components clean.
// src/services/authService.ts
import axios from 'axios';
const API_BASE_URL = 'http://localhost:8000/api';
export interface JwtResponse {
access: string;
refresh: string;
user: {
pk: number;
email: string;
username: string;
};
}
export const exchangeGoogleToken = async (googleAccessToken: string): Promise<JwtResponse> => {
const response = await axios.post<JwtResponse>(
`${API_BASE_URL}/v1/auth/social/google/`,
{ access_token: googleAccessToken }
);
return response.data;
};
Authentication Component: Implement the login button with error handling.
// src/components/AuthButton.tsx
import React, { useState } from 'react';
import { GoogleOAuthProvider, useGoogleLogin } from '@react-oauth/google';
import { exchangeGoogleToken } from '../services/authService';
const GoogleSignInButton: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const login = useGoogleLogin({
onSuccess: async (tokenResponse) => {
setLoading(true);
setError(null);
try {
const result = await exchangeGoogleToken(tokenResponse.access_token);
// Store JWT securely. For production, consider httpOnly cookies.
localStorage.setItem('jwt_access', result.access);
localStorage.setItem('jwt_refresh', result.refresh);
console.log('Authentication successful:', result.user);
} catch (err) {
setError('Authentication failed. Please try again.');
console.error(err);
} finally {
setLoading(false);
}
},
onError: () => {
setError('Login cancelled or failed by Google.');
},
});
return (
<div>
<button
onClick={() => login()}
disabled={loading}
style={{ padding: '10px 20px', cursor: loading ? 'not-allowed' : 'pointer' }}
>
{loading ? 'Processing...' : 'Sign in with Google'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
};
export const App: React.FC = () => {
return (
<GoogleOAuthProvider clientId="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com">
<GoogleSignInButton />
</GoogleOAuthProvider>
);
};
6. Runtime Configuration via Django Admin
The integration requires runtime configuration in the Django Admin interface. This step is mandatory for django-allauth to function.
- Run migrations:
python manage.py migrate.
- Create a superuser:
python manage.py createsuperuser.
- Start the server and navigate to
/admin.
- Sites: Ensure the
example.com site is updated to localhost:8000 or your production domain.
- Social Applications:
- Navigate to Social Accounts > Social Applications.
- Click Add.
- Provider: Select Google.
- Name: Enter a descriptive name (e.g., "Production Google SSO").
- Client id and Secret key: Paste values from Google Cloud Console.
- Sites: Move the configured site from "Available sites" to "Chosen sites".
- Save.
Pitfall Guide
-
Missing SITE_ID or Site Object:
- Explanation:
django-allauth relies on the sites framework to associate social applications with domains. If SITE_ID is missing or the Site object doesn't exist, the provider lookup fails.
- Fix: Ensure
SITE_ID = 1 is in settings and the Site record exists in the database with the correct domain.
-
CORS Rejection on Token Exchange:
- Explanation: The React app runs on a different port/domain. Without proper CORS headers, the browser blocks the POST request to the Django API.
- Fix: Install
django-cors-headers, add to INSTALLED_APPS, and configure CORS_ALLOWED_ORIGINS to include the React origin.
-
Admin Social Application Omission:
- Explanation: Developers often configure
SOCIALACCOUNT_PROVIDERS in settings but forget to add the SocialApplication record in the Admin. The API will return a 400 error because the client credentials are not found at runtime.
- Fix: Always provision the Social Application in the Admin after migrations.
-
JWT vs Session Mode Mismatch:
- Explanation:
dj-rest-auth defaults to session authentication. If USE_JWT is not set to True in REST_AUTH, the response will not contain JWTs, breaking the SPA flow.
- Fix: Explicitly set
'USE_JWT': True in the REST_AUTH dictionary.
-
Scope Misconfiguration:
- Explanation: If
email is not included in the SCOPE list, Google may not return the user's email address, causing user creation to fail or require manual input.
- Fix: Include
'email' in SOCIALACCOUNT_PROVIDERS['google']['SCOPE'].
-
Token Storage Security:
- Explanation: Storing JWTs in
localStorage exposes them to XSS attacks.
- Fix: For production, configure
JWT_AUTH_COOKIE and JWT_AUTH_HTTPONLY to store tokens in secure, httpOnly cookies. Update the React app to rely on automatic cookie transmission.
-
Authorized Origins vs Redirect URIs Confusion:
- Explanation: In Google Cloud Console, "Authorized JavaScript Origins" restricts the domain making the request, while "Authorized Redirect URIs" restricts the callback. Misconfiguring these causes Google to reject the request.
- Fix: Add the React origin to Origins and the callback route to Redirect URIs.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| SPA with JWT | Token Exchange via dj-rest-auth | Decouples frontend from backend sessions; scalable. | Low infrastructure cost. |
| Server-Side Rendered | Session-based allauth | Simpler state management; cookies handle auth automatically. | Reduced frontend complexity. |
| Multiple Providers | allauth Social Accounts | Unified interface for Google, GitHub, Apple, etc. | Minimal incremental dev cost. |
| High Security | HttpOnly Cookies + CSRF | Mitigates XSS token theft; requires CSRF protection. | Moderate frontend/backend config effort. |
Configuration Template
Use this template for environment variables and settings management.
# .env
DJANGO_SECRET_KEY=your-super-secret-key
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://yourdomain.com
# settings.py snippet
import os
from dotenv import load_dotenv
load_dotenv()
REST_AUTH = {
'USE_JWT': True,
'JWT_AUTH_COOKIE': 'platform-auth',
'JWT_AUTH_HTTPONLY': False,
'JWT_AUTH_REFRESH_COOKIE': 'platform-auth-refresh',
}
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': ['profile', 'email'],
'AUTH_PARAMS': {'access_type': 'online'},
}
}
CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', '').split(',')
Quick Start Guide
- Backend Setup:
django-admin startproject platform_api .
cd platform_api
python manage.py startapp identity
pip install django djangorestframework django-allauth dj-rest-auth djangorestframework-simplejwt django-cors-headers
python manage.py migrate
python manage.py createsuperuser
- Google Console: Create project, configure OAuth consent screen, generate Web Client ID, and note credentials.
- Admin Config: Start server, log in to
/admin, update Site, and add Social Application with Google credentials.
- Frontend Setup:
npm create vite@latest react-spa -- --template react-ts
cd react-spa
npm install @react-oauth/google axios
- Integration: Implement the
AuthButton component and authService as shown in the Core Solution. Run npm run dev and test the login flow.