To use a custom database model as an HTTP header token, the database model itself does not automatically read request headers. In a web application framework like Django REST Framework (DRF), the configuration is split into two distinct responsibilities:
1. The Database Model: Stores and validates the API key (ideally stored as a secure hash rather than plain text).
2. The Authentication Class / Middleware: Reads incoming HTTP headers (such as Authorization: Api-Key <token> or X-Api-Key: <token>), extracts the value, hashes it, and queries the database model.
Here is how you set up both components and why each piece works the way it does.
Step 1: Define the Database Model
When creating the model, we store a hashed key rather than raw plaintext so that a database breach does not leak raw tokens. We also store a prefix or mask to identify the key in UI logs.
import secrets
import hashlib
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class APIKey(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="api_keys")
name = models.CharField(max_length=50)
key_hash = models.CharField(max_length=128, unique=True, editable=False)
prefix = models.CharField(max_length=8, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
@classmethod
def generate_key(cls, user, name):
"""
Generates a new API key.
Returns a tuple: (APIKey instance, raw_key string)
"""
# Generate a cryptographically secure random token
raw_key = f"sk_live_{secrets.token_urlsafe(32)}"
prefix = raw_key[:8]
# Hash the token with SHA-256 before saving
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
api_key_obj = cls.objects.create(
user=user,
name=name,
prefix=prefix,
key_hash=key_hash
)
# Return raw_key ONLY ONCE to show the user
return api_key_obj, raw_key
def __str__(self):
return f"{self.name} ({self.prefix}...)"
Step 2: Implement the Header Extraction Authentication Class
To hook this model into incoming requests, we write a custom authentication class that inherits from rest_framework.authentication.BaseAuthentication.
This class intercepts incoming headers, extracts the token, and matches its hash against APIKey.key_hash.
import hashlib
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import APIKey
class APIKeyHeaderAuthentication(BaseAuthentication):
"""
Custom authentication class that parses the API key from
either the 'X-Api-Key' header or the standard 'Authorization' header.
"""
def authenticate(self, request):
# 1. Look for custom header 'X-Api-Key'
api_key = request.META.get('HTTP_X_API_KEY')
# 2. Fallback: Look for 'Authorization: Api-Key <token>'
if not api_key:
auth_header = request.META.get('HTTP_AUTHORIZATION')
if auth_header and auth_header.startswith('Api-Key '):
api_key = auth_header.split(' ')[1]
# If no key in header, return None to allow unauthenticated or next auth class
if not api_key:
return None
# 3. Hash the incoming key to match the database stored hash
hashed_input_key = hashlib.sha256(api_key.encode()).hexdigest()
# 4. Query database for matching active key
try:
key_obj = APIKey.objects.select_related('user').get(
key_hash=hashed_input_key,
is_active=True
)
except APIKey.DoesNotExist:
raise AuthenticationFailed('Invalid or inactive API key.')
# 5. Return (user, auth) tuple to set request.user and request.auth
return (key_obj.user, key_obj)
def authenticate_header(self, request):
"""
Returns a string to be used as the value of the WWW-Authenticate
header in a 401 Unauthenticated response.
"""
return 'Api-Key realm="API"'
Step 3: Configure Views or Global Settings
You can apply this authentication strategy globally in your settings.py or per view.
Option A: Per View Basis
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .authentication import APIKeyHeaderAuthentication
class SecureDataView(APIView):
authentication_classes = [APIKeyHeaderAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({
"message": "Authenticated successfully!",
"user": request.user.username,
"key_prefix": request.auth.prefix
})
Option B: Global REST Framework Settings (settings.py)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'your_app.authentication.APIKeyHeaderAuthentication',
'rest_framework.authentication.SessionAuthentication', # Optional fallback
],
}
How the Request Header Flow Works
When sending HTTP requests from a client (Postman, Frontend, Curl), send the key using one of the configured headers:
bash
# Using custom header
curl -H "X-Api-Key: sk_live_your_raw_token_here" https://api.yourdomain.com/secure-data/
# OR using Authorization header
curl -H "Authorization: Api-Key sk_live_your_raw_token_here" https://api.yourdomain.com/secure-data/
Top comments (0)