stack/docs/templates-python/concepts/backend-integration.mdx
2025-09-05 02:23:17 -05:00

452 lines
15 KiB
Plaintext

---
title: "Backend Integration"
description: "Learn how to integrate Stack Auth's backend into your Python application"
---
To authenticate your Python server endpoints, you need to send the user's access token in the headers of the request to your server, and then make a request to Stack Auth's server API to verify the user identity.
## Sending requests to your server endpoints
On the client side (frontend), you can retrieve the access token from the `user` object by calling `user.getAuthJson()`. This will return an object containing `accessToken`.
Then, you can call your Python server endpoint with the access token in the headers:
```typescript
// Frontend code
const { accessToken } = await user.getAuthJson();
const response = await fetch('/api/users/me', {
headers: {
'X-Stack-Access-Token': accessToken,
},
// your other options and parameters
});
```
## Authenticating users on Python server endpoints
Stack Auth provides two methods for authenticating users on your Python server endpoints:
1. **JWT Verification**: A fast, lightweight approach that validates the user's token locally without making external requests. Ideal for high-performance applications.
2. **REST API Verification**: Makes a request to Stack Auth's servers to validate the token and retrieve comprehensive user information. Best when you need complete, up-to-date user data.
### Using JWT
JWT verification is faster and reduces external dependencies. Install the required packages:
```bash
pip install PyJWT[crypto] requests
```
Here's how to implement JWT verification in your Python backend:
```python
import jwt
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
# You can cache this and refresh it with a low frequency
jwks_client = PyJWKClient("https://api.stack-auth.com/api/v1/projects/<your-project-id>/.well-known/jwks.json")
def verify_jwt_token(access_token):
"""
Verify JWT token and extract user information
Returns user data or None if invalid
"""
try:
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
payload = jwt.decode(
access_token,
signing_key.key,
algorithms=["ES256"],
audience="<your-project-id>",
)
return {
'user_id': payload['sub'],
'is_anonymous': payload.get('role') == 'anon'
}
except InvalidTokenError:
return None
except Exception:
return None
# Example usage
access_token = 'access token from the headers'
user_data = verify_jwt_token(access_token)
if user_data:
print(f'Authenticated user with ID: {user_data["user_id"]}')
else:
print('Invalid user')
```
Now you can use this JWT verification in your Python web framework. Here are examples for different frameworks:
<Tabs defaultValue="flask">
<TabsList>
<TabsTrigger value="flask">Flask</TabsTrigger>
<TabsTrigger value="fastapi">FastAPI</TabsTrigger>
<TabsTrigger value="django">Django</TabsTrigger>
</TabsList>
<TabsContent value="flask">
```python
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
def authenticate_user(request):
"""Extract and verify access token from request headers"""
access_token = request.headers.get('X-Stack-Access-Token')
if not access_token:
return None
return verify_jwt_token(access_token)
def require_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
user = authenticate_user(request)
if not user:
return jsonify({'error': 'Unauthorized'}), 401
return f(user, *args, **kwargs)
return decorated_function
@app.route('/api/users/me')
@require_auth
def get_current_user(user):
return jsonify({
'user_id': user['user_id'],
'is_anonymous': user['is_anonymous']
})
```
</TabsContent>
<TabsContent value="fastapi">
```python
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
app = FastAPI()
async def get_current_user(x_stack_access_token: Optional[str] = Header(None)):
if not x_stack_access_token:
raise HTTPException(status_code=401, detail="Access token required")
user_data = verify_jwt_token(x_stack_access_token)
if not user_data:
raise HTTPException(status_code=401, detail="Invalid access token")
return user_data
@app.get("/api/users/me")
async def read_current_user(user: dict = Depends(get_current_user)):
return {
"user_id": user["user_id"],
"is_anonymous": user["is_anonymous"]
}
```
</TabsContent>
<TabsContent value="django">
```python
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
def authenticate_user(request):
"""Extract and verify access token from request headers"""
access_token = request.META.get('HTTP_X_STACK_ACCESS_TOKEN')
if not access_token:
return None
return verify_jwt_token(access_token)
@csrf_exempt
def protected_view(request):
user = authenticate_user(request)
if not user:
return JsonResponse({'error': 'Unauthorized'}, status=401)
return JsonResponse({
'user_id': user['user_id'],
'is_anonymous': user['is_anonymous']
})
# Or as a decorator
def auth_required(view_func):
def wrapper(request, *args, **kwargs):
user = authenticate_user(request)
if not user:
return JsonResponse({'error': 'Unauthorized'}, status=401)
request.user = user
return view_func(request, *args, **kwargs)
return wrapper
@auth_required
def my_protected_view(request):
return JsonResponse({'message': f'Hello, {request.user["user_id"]}!'})
```
</TabsContent>
</Tabs>
### Using the REST API
For cases where you need complete user information including email, you can use the `stack_auth_request` helper function from the [setup guide](../getting-started/setup):
```python
def authenticate_user_with_api(access_token):
"""
Authenticate user and get complete profile via REST API
Returns full user information including email, display name, etc.
"""
try:
user_data = stack_auth_request('GET', 'api/v1/users/me', headers={
'x-stack-access-token': access_token
})
return {
'id': user_data['id'],
'display_name': user_data['display_name'],
'primary_email': user_data['primary_email'],
'primary_email_verified': user_data['primary_email_verified'],
'profile_image_url': user_data['profile_image_url'],
'signed_up_at_millis': user_data['signed_up_at_millis'],
'last_active_at_millis': user_data['last_active_at_millis'],
'has_password': user_data['has_password'],
'is_anonymous': user_data['is_anonymous'],
'oauth_providers': user_data['oauth_providers']
}
except Exception as e:
print(f"Authentication failed: {e}")
return None
# Example usage
access_token = request.headers.get('X-Stack-Access-Token')
if access_token:
user_info = authenticate_user_with_api(access_token)
if user_info:
print(f"Authenticated user: {user_info['primary_email']}")
print(f"Display name: {user_info['display_name']}")
else:
print("Authentication failed")
```
## Environment Configuration
As shown in the [setup guide](../getting-started/setup), make sure you have your Stack Auth credentials configured:
```python
import os
stack_project_id = os.getenv("STACK_PROJECT_ID")
stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
```
And ensure you have the `stack_auth_request` helper function available from the setup guide.
## Error Handling Best Practices
```python
from enum import Enum
from django.http import JsonResponse
class AuthError(Enum):
MISSING_TOKEN = "Access token required"
INVALID_TOKEN = "Invalid or expired access token"
SERVER_ERROR = "Authentication server error"
def safe_authenticate_user(request):
"""
Robust authentication with proper error handling
"""
access_token = request.headers.get('X-Stack-Access-Token')
if not access_token:
return None, AuthError.MISSING_TOKEN
try:
user_data = verify_jwt_token(access_token)
if user_data:
return user_data, None
else:
return None, AuthError.INVALID_TOKEN
except Exception as e:
print(f"Authentication error: {e}")
return None, AuthError.SERVER_ERROR
# Usage in your endpoints
def protected_endpoint(request):
user, error = safe_authenticate_user(request)
if error:
return JsonResponse({'error': error.value}, status=401)
# User is authenticated, proceed with your logic
return JsonResponse({'user': user})
```
## Complete Backend Integration Example
Here's a comprehensive example that demonstrates both JWT and REST API authentication working together:
```python
import os
import jwt
import requests
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
from enum import Enum
# Setup (from setup guide)
stack_project_id = os.getenv("STACK_PROJECT_ID")
stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
if not stack_project_id:
raise RuntimeError("STACK_PROJECT_ID is not set")
def stack_auth_request(method, endpoint, **kwargs):
res = requests.request(
method,
f'https://api.stack-auth.com/{endpoint}',
headers={
'x-stack-access-type': 'server',
'x-stack-project-id': stack_project_id,
'x-stack-publishable-client-key': stack_publishable_client_key,
'x-stack-secret-server-key': stack_secret_server_key,
**kwargs.pop('headers', {}),
},
timeout=10,
**kwargs,
)
if res.status_code >= 400:
raise Exception(f"Stack Auth API request failed with {res.status_code}: {res.text}")
return res.json()
# JWT verification setup
jwks_client = PyJWKClient(f"https://api.stack-auth.com/api/v1/projects/{stack_project_id}/.well-known/jwks.json")
def verify_jwt_token(access_token):
"""Fast JWT verification - returns basic user info"""
try:
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
payload = jwt.decode(
access_token,
signing_key.key,
algorithms=["ES256"],
audience=stack_project_id
)
return {
'user_id': payload['sub'],
'is_anonymous': payload.get('role') == 'anon'
}
except (InvalidTokenError, Exception):
return None
def get_full_user_info(access_token):
"""REST API call - returns complete user profile"""
try:
user_data = stack_auth_request('GET', 'api/v1/users/me', headers={
'x-stack-access-token': access_token
})
return user_data
except Exception:
return None
class AuthenticationService:
@staticmethod
def authenticate_request(request, require_full_profile=False):
"""
Authenticate a request with optional full profile retrieval
Args:
request: The HTTP request object
require_full_profile: If True, fetches complete user info via REST API
Returns:
User data dictionary or None if authentication fails
"""
access_token = request.headers.get('X-Stack-Access-Token')
if not access_token:
return None
if require_full_profile:
# Use REST API for complete user information
return get_full_user_info(access_token)
else:
# Use JWT for fast authentication
return verify_jwt_token(access_token)
@staticmethod
def require_auth(require_full_profile=False):
"""Decorator for protecting endpoints"""
def decorator(func):
def wrapper(request, *args, **kwargs):
user = AuthenticationService.authenticate_request(request, require_full_profile)
if not user:
return {'error': 'Unauthorized'}, 401
return func(request, user, *args, **kwargs)
return wrapper
return decorator
# Example usage in different scenarios
@AuthenticationService.require_auth(require_full_profile=False)
def fast_protected_endpoint(request, user):
"""Fast endpoint using JWT verification"""
return {
'message': f'Hello user {user["user_id"]}!',
'is_anonymous': user['is_anonymous']
}
@AuthenticationService.require_auth(require_full_profile=True)
def profile_endpoint(request, user):
"""Endpoint that needs complete user info"""
return {
'user_id': user['id'],
'display_name': user['display_name'],
'email': user['primary_email'],
'email_verified': user['primary_email_verified'],
'profile_image': user['profile_image_url'],
'is_anonymous': user['is_anonymous']
}
# Error handling example
class AuthError(Enum):
MISSING_TOKEN = "Access token required"
INVALID_TOKEN = "Invalid or expired access token"
SERVER_ERROR = "Authentication server error"
def safe_authenticate(request, require_full_profile=False):
"""Authentication with comprehensive error handling"""
access_token = request.headers.get('X-Stack-Access-Token')
if not access_token:
return None, AuthError.MISSING_TOKEN
try:
if require_full_profile:
user_data = get_full_user_info(access_token)
else:
user_data = verify_jwt_token(access_token)
if user_data:
return user_data, None
else:
return None, AuthError.INVALID_TOKEN
except Exception as e:
print(f"Authentication error: {e}")
return None, AuthError.SERVER_ERROR
```
## Performance Considerations
- **JWT Verification**: Faster, no external requests, but limited user data (only `user_id` and `is_anonymous`)
- **REST API Verification**: Slower, requires network calls, but provides complete user information including email, profile, etc.
- **Hybrid Approach**: Use JWT for basic authentication, then fetch full profile only when needed
- **Caching**: Consider caching JWKs and user data for better performance
- **Connection Pooling**: Use session objects for REST API calls to reuse connections
Choose the appropriate method based on your endpoint's requirements:
- Use **JWT** for high-performance endpoints that only need user ID
- Use **REST API** when you need complete user profiles, email verification status, etc.
- Use **hybrid approach** to optimize performance while maintaining flexibility