Mobile Authentication
JWT-based authentication for mobile and native app clients using allauth app endpoints
Mobile Authentication
Mobile and native app authentication uses JWT tokens via the allauth app client endpoints.
JWT Token Strategy
Enable JWT authentication:
HEADLESS_TOKEN_STRATEGY=apps.accounts.token_strategy.HeadlessJWTTokenStrategyJWT Configuration
# config/settings.py
# Token expiration
HEADLESS_JWT_ACCESS_TOKEN_EXPIRES_IN = 300 # 5 minutes
HEADLESS_JWT_REFRESH_TOKEN_EXPIRES_IN = 86400 # 24 hours
# Stateful validation (recommended)
HEADLESS_JWT_STATEFUL_VALIDATION_ENABLED = True
# Private key (base64 encoded in production)
HEADLESS_JWT_PRIVATE_KEY = "<base64_encoded_pem_key>"API Endpoints
Mobile clients use the /_allauth/app/v1/ endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/auth/login | POST | Authenticate and get tokens |
/auth/signup | POST | Create account |
/auth/session | GET | Get current session |
/auth/session | DELETE | Logout |
/auth/token/refresh | POST | Refresh access token |
Authentication Flow
Login
POST /_allauth/app/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}Response
{
"meta": {
"is_authenticated": true,
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"refresh_token": "eyJhbGciOiJFUzI1NiIs..."
},
"data": {
"user": {
"id": 1,
"email": "user@example.com"
}
}
}Authenticated Requests
GET /api/v1/users/profile/
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...Token Refresh
POST /_allauth/app/v1/auth/token/refresh
Content-Type: application/json
{
"refresh": "eyJhbGciOiJFUzI1NiIs..."
}JWT Token Claims
The custom HeadlessJWTTokenStrategy adds tenant claims to JWT tokens:
# apps/accounts/token_strategy.py
class HeadlessJWTTokenStrategy(JWTTokenStrategy):
def get_claims(self, user) -> Dict[str, Any]:
claims = {}
if hasattr(user, "get_primary_tenant"):
tenant = user.get_primary_tenant()
if tenant:
claims["tenant_id"] = str(tenant.id)
return claimsDecoded Token
{
"sub": "1",
"sid": "abc123",
"iat": 1704672000,
"exp": 1704672300,
"jti": "unique-token-id",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}Client Implementation
React Native Example
import AsyncStorage from '@react-native-async-storage/async-storage';
import axios from 'axios';
const API_URL = 'https://api.example.com';
const apiClient = axios.create({
baseURL: API_URL,
});
// Token storage
async function storeTokens(access: string, refresh: string) {
await AsyncStorage.multiSet([
['access_token', access],
['refresh_token', refresh],
]);
}
// Request interceptor
apiClient.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor for token refresh
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
const refresh = await AsyncStorage.getItem('refresh_token');
if (refresh) {
const { data } = await axios.post(
`${API_URL}/_allauth/app/v1/auth/token/refresh`,
{ refresh }
);
await storeTokens(
data.meta.access_token,
data.meta.refresh_token
);
// Retry original request
error.config.headers.Authorization =
`Bearer ${data.meta.access_token}`;
return apiClient.request(error.config);
}
}
return Promise.reject(error);
}
);
// Login function
async function login(email: string, password: string) {
const { data } = await axios.post(
`${API_URL}/_allauth/app/v1/auth/login`,
{ email, password }
);
await storeTokens(
data.meta.access_token,
data.meta.refresh_token
);
return data.data.user;
}Swift Example
import Foundation
class AuthService {
static let shared = AuthService()
private let baseURL = "https://api.example.com"
func login(email: String, password: String) async throws -> User {
let url = URL(string: "\(baseURL)/_allauth/app/v1/auth/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode([
"email": email,
"password": password
])
let (data, _) = try await URLSession.shared.data(for: request)
let response = try JSONDecoder().decode(LoginResponse.self, from: data)
// Store tokens in Keychain
KeychainService.store(key: "access_token", value: response.meta.accessToken)
KeychainService.store(key: "refresh_token", value: response.meta.refreshToken)
return response.data.user
}
}Security Considerations
- Token Storage: Store tokens securely (Keychain on iOS, EncryptedSharedPreferences on Android)
- Token Refresh: Implement automatic refresh before expiration
- Logout: Clear all stored tokens on logout
- SSL Pinning: Consider certificate pinning for sensitive apps
- Biometric Auth: Use biometrics to unlock stored tokens
Testing
# tests/account/test_jwt_auth.py
class TestJWTLogin:
def test_login_returns_jwt_tokens(self, dj_client, user_with_tenant):
response = dj_client.post(
"/_allauth/app/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
)
assert response.status_code == 200
assert "access_token" in response.json()["meta"]
assert "refresh_token" in response.json()["meta"]Last updated on