What is the point of having "logout" endpoint in django-rest-auth? - django

I this article I have read that token based authentication is stateless, meaning that the servers don't keep record of logged in users.
On the other hand in the django-rest-auth API docs there is a logout endpoint mentioned. What is it for?

During log out, the authentication token issued to the user is deleted. You can check out the logout source where it calls request.user.auth_token.delete(). Therefore, the next time the user logs in, a new token will be issued.

In the event you are using the JWT option with django-rest-auth, the logout behavior doesn't actually appear to delete the JWT tokens. So it seems to be essentially doing nothing. Now I'm still new to JWT, but based on what I've learned - it appears that this isn't even necessary. So just delete on the client and you'll be okay. Still it would be nice to have a JUST-DESTROY-THE-JWT-TOKEN-BECAUSE-WHO-NEEDS-POTENTIALLY-UNEXPIRED-DANGEROUS-STUFF-HANGING-AROUND option. But sadly, I don't think rest_framework_jwt supports this.
rest_framework_simplejwt (https://github.com/davesque/django-rest-framework-simplejwt) does seem to support this "blacklisting" which is promising! (rest_framework_simplejwt.token_blacklist) And yet, alas rest_auth does not support simplejwt, straight up - ah well. If someone figures out how to do this, I'd be game.
But back to what's going on with rest-auth's jwt implementation...
So when you are logging out, it does attempt to delete the django token, but I don't believe that's what you are doing here since you should have the following in your settings.py if you are using the JSONWebTokenAuthentication class:
settings.py (for JWT)
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # USED BY JWT.
'rest_framework.authentication.TokenAuthentication', # IGNORED BY JWT
)
Now when django-rest-auth goes to execute /logout, let's see what they are doing from their source code on https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/views.py
views.py (from rest-auth source)
from django.contrib.auth import (
login as django_login,
logout as django_logout
)
from .models import TokenModel
#-------snip-------
class LogoutView(APIView):
"""
Calls Django logout method and delete the Token object
assigned to the current User object.
Accepts/Returns nothing.
"""
permission_classes = (AllowAny,)
def get(self, request, *args, **kwargs):
if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False):
response = self.logout(request)
else:
response = self.http_method_not_allowed(request, *args, **kwargs)
return self.finalize_response(request, response, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.logout(request)
def logout(self, request):
try:
request.user.auth_token.delete()
except (AttributeError, ObjectDoesNotExist):
pass
if getattr(settings, 'REST_SESSION_LOGIN', True):
django_logout(request)
response = Response({"detail": _("Successfully logged out.")},
status=status.HTTP_200_OK)
if getattr(settings, 'REST_USE_JWT', False):
from rest_framework_jwt.settings import api_settings as jwt_settings
if jwt_settings.JWT_AUTH_COOKIE:
response.delete_cookie(jwt_settings.JWT_AUTH_COOKIE)
return response
models.py (from rest-auth source)
from django.conf import settings
from rest_framework.authtoken.models import Token as DefaultTokenModel
from .utils import import_callable
# Register your models here.
TokenModel = import_callable(
getattr(settings, 'REST_AUTH_TOKEN_MODEL', DefaultTokenModel))
So unless you've set the REST_AUTH_TOKEN_MODEL attribute in your settings to a custom token model (see https://github.com/Tivix/django-rest-auth/blob/master/docs/configuration.rst - which is doubtful, let's all be honest here. Who is doing this...), then it's just trying to delete the DefaultTokenModel - which is the django_token/session authentication model we aren't using.
I do hope things migrate to simplejwt as it's much more actively supported.
And I also hope this brain-dump/jwt digging helps someone.
P.S. A little more on JWT, optimal uses and blacklisting. https://dev.to/_arpy/how-to-log-out-when-using-jwt-4ajm

Related

Allow both authenticated and unauthenticated users access a django rest view with token authentication decorator

I am working with django rest, I however have an issue in one of my views because, i want to allow both authenticated users and unauthenticated users access the view then check if the user is authenticated then there are some special events to be done by the celery tasks, however, whenever i add the decorator for the authentication_classes, unauthenticated users can no longer visit the page even after setting the permission_classes to allow all
simply my code is here, hope someone can know what i need to add or remove
#api_view(['GET'])
#permission_classes([AllowAny])
#authentication_classes([TokenAuthentication])
def item_details(request, pk):
if request.user.is_authenticated:
#here some tasks
the main issue is that it seams that TokenAuthentication just nullify AllowAny and takes over the checking of the permisssion class
or is there something am doing wrong?
Overriding the authenticate method of TokenAuthentication
myauth.py
from rest_framework.authentication import TokenAuthentication
class TokenAuthenticationSafe(TokenAuthentication):
def authenticate(self, request):
try:
return super().authenticate(request=request)
except:
return None
views.py
from myauth import TokenAuthenticationSafe
#api_view(['GET'])
#permission_classes([AllowAny])
#authentication_classes([TokenAuthenticationSafe])
def item_details(request, pk):
if request.user.is_authenticated:
#here some tasks

Get username in a middleware from Django Rest Framework SIMPLE JWT token (3rd party)

I am using Django Rest Framework and I've included a 3rd party package called REST framework simple JWT Auth which is the new framework referenced,
and this one, REST framework JWT Auth, which is the old one (I Imagine), since there was no update on github since a long time and maybe not supported for newer versions.
And I'm looking for a way, like this link on stackoverflow-3rd answer, via middlewares, to get the user information for each request in order to apply/save it, in needed, the user object in my models by using django signals.
I checked in documentation and on internet, but I didn't find anything. So, if you already had that case, I will appreciate your help.
Thank you
To get username from the user model you should use request.user. This will give you the authenticated user's info with the request. However if you use simple_jwt, it's not possible to directly use request.user in a middleware because authentication mechanism works in view function.
So you should manually authenticate inside the middleware and then you can use request.user to get whatever data from the user model.
from rest_framework_simplejwt import authentication
class MyMiddleware():
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
request.user = authentication.JWTAuthentication().authenticate(request)[0] # Manually authenticate the token
print(request.user) # Use the request.user as you want
With simple_jwt the user information will not be set on the request initially but after the request is processed it will be. So if you're just logging the information and you can do it after the request is processed, do it there.
So for example in a simple middleware:
def simple_middleware(get_response):
def middleware(request):
# request.user is an anonymous user at this point
response = get_response(request)
# request.uesr is the logged-in user here
return response
return middleware

python-social-auth, logout without disconnect

Upon logout, wouldn't it be natural to remove the access tokens for social login?
(User will be able to login with different social account next time he logs in)
How do I do it with python-social-auth?
https://python-social-auth.readthedocs.org/en/latest/pipeline.html#disconnection-pipeline talks about disconnecting and I guess it is close to closing the account than to logout
Logout with django-social-auth asks the same question, but answers don't actually address his question.
Just use the default django logout for this, even for login if you see the code inside, it ask the data from OP and create adjango login from that data. and store all the info in socialauth user.
And also python-social-auth have class for socialauth user and storage at backend, which store manythings, so for reference you can check the models.py and storage.py inside social_django.
In my current project which use django and social-auth-app-django of python-social-auth, i am usein gthe default django logout,
from django.contrib.auth import logout as auth_logout
class logout(TemplateView):
next_page = settings.LOGOUT_REDIRECT_URL
redirect_field_name = REDIRECT_FIELD_NAME
template_name = None
extra_context = None
#method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
auth_logout(request)
next_page = settings.LOGOUT_REDIRECT_URL
if next_page:
return HttpResponseRedirect(next_page)
return super().dispatch(request, *args, **kwargs)

django-allauth adapter redirects

I am leveraging django-allauth to provide Google authentication for my property management application. Here is the registration workflow I'm looking for:
A new manager goes to a registration page with a "Signup With Google" button.
They click the button and sign into Google.
On the call back from Google they are presented with a form for additional info.
When they submit this form their user account and manager profile is created and they are redirected to their company's homepage.
I have attempted to handle this "redirect to company's homepage" through adapters. The problem I am having is that the "get_login_redirect_url" function is executed after the initial Google sign-in, but before the signup form where I collect their work schedule, so I get a DoesNotExist on the adapter redirect because it was called before the managerprofile was created.
What is the proper way to do these redirects?
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'managers.signup.ManagerProfileSignupForm'
SOCIALACCOUNT_AUTO_SIGNUP = False
SOCIALACCOUNT_ADAPTER = 'managers.adapter.ManagerSocialAccountAdapter'
ACCOUNT_ADAPTER = 'managers.adapter.ManagerAccountAdapter'
adapters.py
class ManagerSocialAccountAdapter(DefaultSocialAccountAdapter):
def get_connect_redirect_url(self, request, socialaccount):
return reverse('company_details', args=(request.user.managerprofile.company.pk,))
class ManagerAccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
return reverse('company_details', args=(request.user.managerprofile.company.pk,))
Set the LOGIN_REDIRECT_URL on the settings.py of your application:
I have this value, to redirect to the home page:
LOGIN_REDIRECT_URL = '/'
you need to define a decorator which consists of a function which runs before the account is created. Here take a look
#imports necessary for decorator call
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.signals import pre_social_login
from allauth.account.utils import perform_login
from django.dispatch import receiver
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialLogin
# defining class to run through authentication
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
pass
# reciever defining function to hold the account before making it registered
#receiver(pre_social_login)
def link_to_local_user(sender, request, sociallogin, **kwargs):
socialemail = sociallogin.user.email
socialuname = socialemail.split('#')[0]
sociallogin.user.username = socialuname+str(sociallogin.user.pk)
if User.objects.filter(email=sociallogin.user.email).exists():
user = User.objects.get(email=sociallogin.user.email)
if user:
perform_login(request, user, email_verification='optional')
raise ImmediateHttpResponse(redirect('homePage'))
else:
SocialLogin.save(sociallogin, request, connect=False)
user = User.objects.get(email=sociallogin.user.email)
perform_login(request, user, email_verification='optional')
raise ImmediateHttpResponse(redirect('homePage'))
This is assuming you have created a signal over User model instance creation which directly creates a profile model instance also mapping the user. If not, below the model for ManagerProfile, use this:
def create_profile(sender, instance, created, **kwargs):
if created:
<ManagerProfileModel>.objects.create(userID=instance)
post_save.connect(create_profile, sender=<UserModelWhereMainAccountIsCreated>)

Django How to prevent multiple users login using the same credentials

I am developing an Django application using django auth module and would like to prevent multiple login using the same user name and password.
It should prevent multiple logins on different machines using the same user name and password. How do I achieve this in Django?
We have to keep following things in mind:
If user close the browser without logging out
If the session times out
You may try this, it logs out the first user and logs in the second. Add middleware.py in your app directory (same level as models, views etc) and add this code. Useful when the same person is using more than one device. Make sure you add this to your middleware classes: 'myapp.middleware.UserRestrict',
class UserRestrict(object):
def process_request(self, request):
"""
Checks if different session exists for user and deletes it.
"""
if request.user.is_authenticated():
cache = get_cache('default')
cache_timeout = 86400
cache_key = "user_pk_%s_restrict" % request.user.pk
cache_value = cache.get(cache_key)
if cache_value is not None:
if request.session.session_key != cache_value:
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore(session_key=cache_value)
session.delete()
cache.set(cache_key, request.session.session_key,
cache_timeout)
else:
cache.set(cache_key, request.session.session_key, cache_timeout)
Out of the box, Django doesn't provide you with a way to prevent concurrent sessions for the same user account, and that isn't a trivial thing to do. However, here's another question with some suggestions about how you might make this happen: How can I detect multiple logins into a Django web application from different locations?
i solve the problem with a new model, a custom decorator and custom login page
1) i created a additional model for users eg:
class SessionKey(models.Model):
user = models.OneToOneField(User,primary_key=True)
key = models.CharField(max_length=255)
2) i created custom decorator to check session key is equal or not last key.
i changed the original source code django decorators
from functools import wraps
from django.conf import settings
from django.utils.decorators import available_attrs
from django.contrib.auth.decorators import login_required
from django.shortcuts import resolve_url
from users.models import SessionKey #my additional model
def unique_login_required(view_func):
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
r = False
...
#check session key is equal to last one
...
if r:
return view_func(request, *args, **kwargs)
else:
from django.contrib.auth.views import redirect_to_login
path = request.build_absolute_uri()
resolved_login_url = resolve_url(settings.LOGIN_URL)
return redirect_to_login(path,resolved_login_url)
return _wrapped_view
3) in custom login page, i updated the session key. last login always updates the stored session key.
finally, in the views, i call my decorator
from users.decorators import unique_login_required
#unique_login_required
def index(request):
...