Reputation: 11
I'm using django-allauth (headless, {app}) as my authentication backend along with django-ninja. I've set-up django-allauth configurations properly. If I login using POST http://127.0.0.1:8000/_allauth/app/v1/auth/login with a valid body (username & pass), it successfully responds with a session token. As instructed in the docs, when I set X-Session-Token header with this token value & send a request to http://localhost:8000/_allauth/app/v1/auth/session (Get authentication status), it also responds successfully with json body containing "meta": {"is_authenticated": true}, meaning the user is authenticated.
So far, everything is fine & the authentication is working perfectly. But, when I hit another api url (for example: GET http://localhost:8000/api/website/blog_category with header X-Session-Token: <TheToken>), then I find from that function, request.user is always AnonymousUser. My understanding was, since django-allauth has its own middleware, it should have populated the request.user correctly since a valid X-Session-Token is correctly sent as a header.
Hers's my urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path("accounts/", include("allauth.urls")),
path("_allauth/", include("allauth.headless.urls")),
path("api/", api.urls),
]
api.py:
from ninja import NinjaAPI
api = NinjaAPI()
api.add_router("website/", "website.api.router")
Here is my website.api.py:
from ninja import Router
from ninja.security import django_auth # setting auth=django_auth, always returns permission denied
from typing import List
from .models import blog_category
from .schema import BlogCategoryRetrieveSchema
router = Router(auth=None) # since django-auth returns permission denied, so lets run without auth to print request.user & request.META.get('HTTP_X_SESSION_TOKEN')
@router.get("/blog_category", response=List[BlogCategoryRetrieveSchema])
async def blog_category_retrieve(request):
print(request.user)
print(request.META.get('HTTP_X_SESSION_TOKEN'))
# logic here
return something
Console says:
AnonymousUser # for print(request.user)
8jdtkzt69n9xp2xlqf9r6hc1eyw78p0k # for print(request.META.get('HTTP_X_SESSION_TOKEN'))
Meaning, session token is properly sent but django-allauth is not authenticating the user. Shouldn't django-allauth have done it automatically from the middleware? or am I missing anything?
I was expecting that django-allauth's middleware would take care of authenticating the user and populate request.user if a valid x-session-token is sent, in every request. Though, allauth's api urls (headless, {app}) are working perfectly; when requesting to other urls, I don't see any authentication being done leaving the request.user as AnonymousUser. I tried:
from ninja.security import django_auth
router = Router(auth=django_auth)
# But setting auth=django_auth, always returns permission denied
Now, I could write a middleware or maybe a function that uses 'Get authentication status' functionality from 'django-allauth' to validate a user & use this mechanism to set 'auth' (ninja). But isn't there any generic way of doing this? Am I missing something?
Upvotes: 1
Views: 383
Reputation: 11
My approach was (this way we can have a little more manual control):
from allauth.headless.account.views import SessionView
class get_user_from_sessionview(SessionView):
def dispatch(self, request, *args, **kwargs):
return request.user
then, we can easily use it like this:
get_user_from_sessionview.as_api_view(client='app')(request) # client is either 'app' or 'browser'
If I remember correctly, it returned the user object.
Upvotes: 0
Reputation: 127
As per this comment from the man himself (pennersr) I wrote a custom authentication to look up user by session and then added that authentical class to my DRF View's authentication_classes
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class SessionTokenAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
session_token = request.META.get('HTTP_X_SESSION_TOKEN')
# Alternately you could get from the request.headers
# session_token = request.headers.get('X-Session-Token')
if not session_token:
return None # authentication did not succeed
try:
s = Session.objects.get(session_key=session_token)
decoded = s.get_decoded()
user = User.objects.get(pk=decoded['_auth_user_id']) # get the user
except (Session.DoesNotExist, User.DoesNotExist):
raise exceptions.AuthenticationFailed('No such user') # raise exception if user does not exist
return (user, None) # authentication successful
And then you need to either add to your DEFAULT_AUTHENTICATION_CLASSES
in settings.py
, or include this in your view (example below)
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated, AllowAny
from myapp.cusom_auth import SessionTokenAuthentication
class MyCreateAPIView(generics.CreateAPIView):
permission_classes = (IsAuthenticated,)
authentication_classes = [SessionTokenAuthentication]
...
Upvotes: 0
Reputation: 169
I had a similar issue and in my case the problem was stemming from a default "django.contrib.sessions.middleware.SessionMiddleware" implementation. It seems that this vanilla implementation looks for session_key
only in cookies of a request. However, when you explicitly pass allauth's "X-Session-Token" header, Django has no idea that it should look for session_key there. The solution that seems to work (with database-based sessions at least) is to inherit from default SessionMiddleware
:
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware as _SessionMiddleware
class SessionMiddleware(_SessionMiddleware):
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
if session_key is None:
session_key = request.headers.get("X-Session-Token")
request.session = self.SessionStore(session_key)
and add this implementation into settings.INSTALLED_APPS in place of the original one.
Disclaimer: I'm very new to Django myself and haven't thought through all the security implications of this solution.
Upvotes: 0
Reputation: 1
Hi this might be an issue similar to mine. A fix is created. Here is the github issue https://github.com/pennersr/django-allauth/issues/3981.
Upvotes: 0