Django multi-app authentication: sharing custom token validation and permissions across separate codebases
I'm building a multi-platform system where I have one central authentication service (let's call it "Auth") and multiple other Django applications (App1, App2, App3) that need to authenticate users with custom tokens and permissions.
Current Setup:
- Auth service: Handles user registration, login, token management
- App1, App2, App3: Separate Django projects with their own database and business logic
- All apps need to validate users authenticated through the Auth service
Authentication Flow:
- User logs in through Auth service → receives token
- User makes requests to App1/App2/App3 with that token
- App1/App2/App3 need to validate the token and get user data
Authentication Model:
class AuthToken(models.Model):
token = models.CharField(max_length=48, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
expires_at = models.DateTimeField(null=True, blank=True)
is_revoked = models.BooleanField(default=False)
# other fields (ip, device_id, device_name, etc...)
Token Configuration:
Settings.py
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'path.to.authentication.AuthTokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Authentication Class
# path/to/AuthTokenAuthentication.py
class AuthTokenAuthentication(BaseAuthentication):
"""
Simple, clean AuthToken authentication.
"""
def authenticate(self, request):
# Get Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION')
if not auth_header or not auth_header.startswith('Bearer '):
return None # No token provided
# rest of my code
The Problem:
App1, App2, App3 need to use the same custom AuthTokenAuthentication
and permission classes
, but they don't have the AuthToken
model or related authentication code.
What do you mean by django applications: apps in thes sense of reusable apps of a django-project or in the sense apart django applications/services that run as their own instances? If I understood correctly the latter one.
If all your apps run on one server but need access to different databases you can create a custom database router, see the django-docs on this topic: https://docs.djangoproject.com/en/5.2/topics/db/multi-db/ An authRouter is explicitly listed as example.
Your auth app could then use one database and the other apps could use another db or each their own database ... .
If, however, your apps run as separate Django-applications (e.g., on different servers), you have two options:
The first Option would be, that each of your django-applications shares the same reusable auth-app and has a custom db-adapter, that will ensure that this app uses another database than the other models of the project. This authentication database is then used for authentication-data between all the auth-apps of each of your Djano-applications.
The Second option would be to use SAML or better OpenId connect to have single-sign-on (SSO). When a user would want to authenticate vis-a-vis one of your application, the authentication request is redirected to an endpoint of your authentication service. There, the user is presented with a login form and authenticates using their credentials. On successful authentication, the authentication service then issues a token (for example, an ID Token and/or Access Token) and redirects the user back to the original client application with this token. The client application verifies the token (usually via the authentication service’s public keys or another endpoint of your auth-application and establishes a session for the user.
Under the assumption that "authentication service" means an app that App1, App2, ... can reach during normal operations, then it's pretty doable. Just query the auth service to verify the token.
The central question becomes whether App1, App2, ... etc. have their own, app-local user objects, i.e. is the user information stored in "local" db or on the auth-service only? Both are doable, but the implementation depends heavily on the outcome of this question.
I'm going to assume you have app-local storage of users, because otherwise I am not sure what the specific problem you're having is.
Centralized auth tokens for decentralized user stores
- Define a non-id/non-pk field that's unique on the user model, like
email
- Require the user to pre-register on App1, App2, ... before being able to use them
- Auth service issues token that's valid for that email address, not for a specific user object (as user objects are app-isolated)
- App1, App2, ... queries the auth service with the user's token, and the auth service will respond with an email address
- Each app resolves the email address into a user object from their own storage
# auth service
class AuthToken(models.Model):
token = models.CharField(max_length=48, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
expires_at = models.DateTimeField(null=True, blank=True)
is_revoked = models.BooleanField(default=False)
email = models.EmailField()
@api_view(['POST'])
def auth_verification_view(request):
token = request.data.get('token')
try:
auth_token = AuthToken.objects.get(token=token, is_revoked=False)
# Check whatever other things you want here, too - expiration, platform-specific stuff, etc.
except AuthToken.DoesNotExist:
# Whatever you want a missing token situation to result in
return Response({'email': auth_token.email})
# app1, app2, etc.
# users/models.py
class User(AbstractUser):
...
email = EmailField()
class Meta:
constraints = [
UniqueConstraint(fields=['email'], name='unique_email'),
]
# authentication/AuthTokenAuthentication.py
class AuthTokenAuthentication(BaseAuthentication):
"""
Simple, clean AuthToken authentication.
"""
def authenticate(self, request):
# Get Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION')
token = auth_header.split(' ', 1)[1].strip()
if not token:
return None
# Query the auth service
try:
res = requests.post(AUTH_SERVICE_URL, json={"token": token}, timeout=5)
except:
...
# Look up the app-local user with the email we got from auth service
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed("No matching user found")
return (user, None)
Note that you should also cache the auth result, either the auth service result or the authenticator's user-tuple depending on your business needs, otherwise your apps' performance can become abysmal.