Let's say the user lands on https://example.com/any/page?token=hhdo28h3do782
.
What's the recommended way to authenticate and login a user with the query string?
I was thinking about creating some sort of catch-all view (I'd also like to know how to do this :D) that calls authenticate()
. Then I would have in place a custom backend that would authenticate the user.
Is this the ideal way to achieve what I want?
Cheers!
To do this, you need to create a custom authentication backend that validates api keys.
In this example, the request
is checked for a valid token automatically. You don't need to modify and of your views at all. This is because it includes custom middleware that authenticates the user.
For brevity, I'm assuming that the valid user tokens are stored in a model that is foreign keyed to the django auth.User
model.
# my_project/authentication_backends.py
from django.contrib import auth
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.contrib.auth.middleware import AuthenticationMiddleware
TOKEN_QUERY_PARAM = "token"
class TokenMiddleware(AuthenticationMiddleware):
def process_request(self, request):
try:
token = request.GET[TOKEN_QUERY_PARAM]
except KeyError:
# A token isn't included in the query params
return
if request.user.is_authenticated:
# Here you can check that the authenticated user has the same `token` value
# as the one in the request. Otherwise, logout the already authenticated
# user.
if request.user.token.key == token:
return
else:
auth.logout(request)
user = auth.authenticate(request, token=token)
if user:
# The token is valid. Save the user to the request and session.
request.user = user
auth.login(request, user)
class TokenBackend(ModelBackend):
def authenticate(self, request, token=None):
if not token:
return None
try:
return User.objects.get(token__key=token)
except User.DoesNotExist:
# A user with that token does not exist
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Now, you can add the paths to AUTHENTICATION_BACKENDS
and MIDDLEWARE
in your settings.py
in addition to any existing backends or middleware you may already have. If you're using the defaults, it would look like this:
MIDDLEWARE = [
# ...
"django.contrib.auth.middleware.AuthenticationMiddleware",
# This is the dotted path to your backend class. For this example,
# I'm pretending that the class is in the file:
# my_project/authentication_backends.py
"my_project.authentication_backends.TokenMiddleware",
# ...
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"my_project.authentication_backends.TokenBackend",
]