OUA Authentication Quick Reference Guide¶
This quick reference provides concise examples for common tasks when working with the OUA Authentication system.
Installation¶
pip install oua-auth
Configuration¶
Minimal Configuration¶
# settings.py
OUA_SSO_URL = 'https://sso.example.com'
OUA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
OUA_CLIENT_ID = 'your-client-id'
# Add to INSTALLED_APPS
INSTALLED_APPS = [
# ... other apps
'oua_auth',
]
# Add middleware
MIDDLEWARE = [
# ... other middleware
'django.contrib.auth.middleware.AuthenticationMiddleware',
'oua_auth.OUAAuthMiddleware',
# ... other middleware
]
# Add authentication backend
AUTHENTICATION_BACKENDS = [
'oua_auth.OUAAuthBackend',
'django.contrib.auth.backends.ModelBackend',
]
Django REST Framework Configuration¶
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'oua_auth.OUAJWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Usage Examples¶
Protect a Django View¶
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
@login_required
def protected_view(request):
return JsonResponse({
'message': 'This is protected content',
'user': request.user.username,
})
Protect a Django REST Framework API¶
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class ProtectedAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({
'message': 'This is protected API content',
'user': request.user.username,
})
Access Token Claims¶
def view_with_token_data(request):
# Access the original token claims
token_claims = request.token_claims
# Example: check if user has specific role
if 'roles' in token_claims and 'admin' in token_claims['roles']:
# Do admin-specific things
pass
# Proceed with normal view logic
return JsonResponse({'message': 'Success'})
Use Internal Token¶
from oua_auth.utils import create_internal_token
def service_to_service_call():
# Create an internal token for service-to-service communication
token = create_internal_token(
user_id='service-account',
additional_claims={'scope': 'read:data'}
)
# Use the token to call another service
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('https://api.example.com/data', headers=headers)
return response.json()
Blacklist a Token¶
from oua_auth.models import BlacklistedToken
def logout_view(request):
# Get the token from the request
token = request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
if token:
# Blacklist the token
BlacklistedToken.add_token_to_blacklist(
token=token,
blacklisted_by=request.user.username,
reason='User logout'
)
# Proceed with normal logout
return JsonResponse({'message': 'Successfully logged out'})
Frontend Integration¶
React Example¶
// api.js - API service configuration
import axios from "axios";
// Create axios instance
const api = axios.create({
baseURL: "https://api.example.com",
});
// Add request interceptor for authentication
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem("auth_token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Add response interceptor for handling auth errors
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
// Redirect to login or refresh token
window.location.href = "/login";
}
return Promise.reject(error);
}
);
export default api;
Authentication Hook (React)¶
// useAuth.js - React hook for authentication
import { useState, useEffect, createContext, useContext } from "react";
import api from "./api";
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check if user is already logged in
const token = localStorage.getItem("auth_token");
if (token) {
fetchUserProfile();
} else {
setLoading(false);
}
}, []);
const fetchUserProfile = async () => {
try {
const response = await api.get("/api/user/profile");
setUser(response.data);
} catch (error) {
console.error("Failed to fetch user profile:", error);
localStorage.removeItem("auth_token");
} finally {
setLoading(false);
}
};
const login = (token) => {
localStorage.setItem("auth_token", token);
fetchUserProfile();
};
const logout = async () => {
try {
await api.post("/api/auth/logout");
} catch (error) {
console.error("Logout error:", error);
} finally {
localStorage.removeItem("auth_token");
setUser(null);
window.location.href = "/login";
}
};
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Mobile Integration¶
Android (Kotlin) Example¶
// ApiService.kt
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import okhttp3.OkHttpClient
import okhttp3.Interceptor
class TokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val token = SessionManager.getToken()
val request = chain.request().newBuilder()
if (token != null) {
request.addHeader("Authorization", "Bearer $token")
}
return chain.proceed(request.build())
}
}
object ApiClient {
private const val BASE_URL = "https://api.example.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(TokenInterceptor())
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// Usage in Repository
interface ApiService {
@GET("protected/data")
suspend fun getProtectedData(): Response<DataModel>
}
class Repository {
private val apiService = ApiClient.retrofit.create(ApiService::class.java)
suspend fun getProtectedData(): Result<DataModel> {
return try {
val response = apiService.getProtectedData()
if (response.isSuccessful) {
Result.success(response.body()!!)
} else {
Result.failure(Exception("API call failed: ${response.code()}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
iOS (Swift) Example¶
// NetworkManager.swift
import Foundation
class NetworkManager {
static let shared = NetworkManager()
private let baseURL = "https://api.example.com"
func request<T: Decodable>(
endpoint: String,
method: String = "GET",
parameters: [String: Any]? = nil,
completion: @escaping (Result<T, Error>) -> Void
) {
guard let url = URL(string: baseURL + endpoint) else {
completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil)))
return
}
var request = URLRequest(url: url)
request.httpMethod = method
// Add token if available
if let token = UserDefaults.standard.string(forKey: "authToken") {
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
// Add parameters for POST requests
if let parameters = parameters, method == "POST" {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
completion(.failure(error))
return
}
}
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "No data", code: -2, userInfo: nil)))
return
}
do {
let decodedResponse = try JSONDecoder().decode(T.self, from: data)
completion(.success(decodedResponse))
} catch {
completion(.failure(error))
}
}.resume()
}
}
// Usage in ViewModel
class UserViewModel {
func fetchProtectedData(completion: @escaping (Result<DataModel, Error>) -> Void) {
NetworkManager.shared.request(endpoint: "/protected/data", completion: completion)
}
func login(username: String, password: String, completion: @escaping (Bool) -> Void) {
NetworkManager.shared.request(
endpoint: "/auth/login",
method: "POST",
parameters: ["username": username, "password": password]
) { (result: Result<AuthResponse, Error>) in
switch result {
case .success(let response):
UserDefaults.standard.set(response.token, forKey: "authToken")
completion(true)
case .failure:
completion(false)
}
}
}
}
Common Patterns¶
Custom Permission Based on Token Claims¶
from rest_framework.permissions import BasePermission
class HasAdminRole(BasePermission):
"""
Permission class to check if user has an admin role in their token.
"""
def has_permission(self, request, view):
# Check if request has token_claims attribute
if not hasattr(request, 'token_claims'):
return False
# Check if roles claim exists and contains admin
roles = request.token_claims.get('roles', [])
return 'admin' in roles
Custom User Creation from Token¶
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import transaction
User = get_user_model()
def create_user_from_token(token_claims):
"""
Custom function to create a user from token claims.
"""
with transaction.atomic():
# Extract data using configured field mappings
email = token_claims.get(settings.OUA_USER_FIELD_MAPPINGS.get('email', 'email'))
username = token_claims.get(settings.OUA_USER_FIELD_MAPPINGS.get('username', 'preferred_username'), email)
# Create or update user
user, created = User.objects.update_or_create(
email=email,
defaults={
'username': username,
'first_name': token_claims.get(settings.OUA_USER_FIELD_MAPPINGS.get('first_name', 'given_name'), ''),
'last_name': token_claims.get(settings.OUA_USER_FIELD_MAPPINGS.get('last_name', 'family_name'), ''),
'is_active': True,
}
)
# Set admin status based on domain
if '@admin.example.com' in email:
user.is_staff = True
user.is_superuser = True
user.save()
return user
Rate Limiting Helper¶
from django.core.cache import cache
from django.conf import settings
import time
def check_rate_limit(key, max_attempts=None, window=None):
"""
Check if a rate limit has been exceeded.
Returns (is_allowed, attempts, reset_time)
"""
# Use settings or defaults
max_attempts = max_attempts or getattr(settings, 'OUA_MAX_AUTH_FAILURES', 5)
window = window or getattr(settings, 'OUA_AUTH_FAILURE_WINDOW', 300) # 5 minutes
cache_key = f"rate_limit:{key}"
now = int(time.time())
# Get current rate limit data or initialize
rate_data = cache.get(cache_key)
if not rate_data:
rate_data = {
'attempts': 0,
'reset_at': now + window
}
# Check if window expired and reset if needed
if now > rate_data['reset_at']:
rate_data = {
'attempts': 0,
'reset_at': now + window
}
# Increment attempt counter
rate_data['attempts'] += 1
# Store updated data
cache.set(cache_key, rate_data, window)
# Return rate limit status
is_allowed = rate_data['attempts'] <= max_attempts
return is_allowed, rate_data['attempts'], rate_data['reset_at']
Debugging Token Helper¶
import base64
import json
def debug_token(token):
"""
Inspect a JWT token without validation.
Helps debugging token-related issues.
"""
try:
# Split token into header, payload, signature
parts = token.split('.')
if len(parts) != 3:
return {'error': 'Invalid token format'}
# Decode header
header_data = parts[0] + '=' * (4 - len(parts[0]) % 4) # Add padding
header_bytes = base64.urlsafe_b64decode(header_data)
header = json.loads(header_bytes)
# Decode payload
payload_data = parts[1] + '=' * (4 - len(parts[1]) % 4) # Add padding
payload_bytes = base64.urlsafe_b64decode(payload_data)
payload = json.loads(payload_bytes)
return {
'header': header,
'payload': payload,
'signature_present': bool(parts[2])
}
except Exception as e:
return {'error': str(e)}
Permissions¶
Require MFA on a single view¶
from rest_framework.views import APIView
from oua_auth.permissions import RequiresACR
class SensitiveView(APIView):
permission_classes = [RequiresACR("mfa")]
Denies with HTTP 401 + an RFC 9470 step-up challenge when the token's acr is
insufficient (set membership — no hierarchy). Strengthens (never weakens) any global OUA_REQUIRED_ACR.
Require MFA on a session/MVT view (redirect instead of 401)¶
from oua_auth.decorators import require_acr
@require_acr("mfa")
def sensitive_settings(request):
...
Session/MVT counterpart to RequiresACR for server-rendered views: instead of
a 401, an insufficient acr redirects the browser to
oua-login?next=<path>&acr_values=mfa so the user can step up and return to
the page they were on. Build the same URL directly with
build_step_up_url(request, "mfa").
Useful URLs and Endpoints¶
Common Admin URLs¶
- Admin Interface:
/admin/ - Token Management:
/admin/oua_auth/blacklistedtoken/ - User Management:
/admin/auth/user/
Common API Endpoints¶
- Protected API:
/api/protected/ - User Profile:
/api/user/profile/ - Logout:
/api/auth/logout/
Security Headers Reference¶
# settings.py - Security Headers Configuration
OUA_SECURITY_HEADERS = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': "default-src 'self'; script-src 'self'; object-src 'none';",
'Permissions-Policy': 'geolocation=(), camera=(), microphone=()',
'Cache-Control': 'no-store, max-age=0',
}
# Enable HSTS (HTTPS Strict Transport Security)
OUA_ENABLE_HSTS = True
OUA_HSTS_SECONDS = 31536000 # 1 year
OUA_HSTS_INCLUDE_SUBDOMAINS = True
OUA_HSTS_PRELOAD = True