So my question is that you have a custom user 'Account' and I'm trying to use Loginview but with only one specific backend but the class does not care about the value passed.
from django.contrib.auth import views as auth_views
from .lists import ProjectCreateView , ProjectUpdateView , ProjectDeleteView
class ClientLogin(auth_views.Loginview):
def form_valid(self, form):
"""Security check complete. Log the user in."""
auth_login(self.request, form.get_user(),'CREA.models.ClientBackend')
return HttpResponseRedirect(self.get_success_url())
urlpatterns = [
path('', ClientLogin.as_view(template_name='authentification/client_login.html',
redirect_authenticated_user=True,next_page='client-home'), name='client-log-in'),
NB: note that the backend works fine if it's the only one specified in the settings but other than that the loginview use all backends one by one, and yes I know that this is the default behaviour but then whats the point of this variable in auth_login
here is the buld in function in django :
def login(request, user, backend=None):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ""
if user is None:
user = request.user
if hasattr(user, "get_session_auth_hash"):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash
and not constant_time_compare(
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
)
):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument or set the "
"`backend` attribute on the user."
)
else:
if not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, "user"):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
Related
This is mine views.py file
class UserAPIView(TemplateView, ListCreateAPIView):
serializer_class = UserSerializer
queryset = UserProfile.objects.all()
template_name = 'users/login.html'
def post(self, request, *args, **kwargs):
if self.find_password_and_user(request):
return HttpResponseRedirect(reverse('user:home'))
else:
return HttpResponse("False")
def find_password_and_user(self, request):
print(request)
post_username = request.data.get('username')
post_password = request.data.get('password')
user = authenticate(username=post_username, password=post_password)
if user is not None:
# login(request, user)
# =====
backend = None
session_auth_hash = ''
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
# =====
user = UserProfile.objects.filter(username=post_username)
u_password = user.values('password')[0].get('password')
return check_password(post_password, u_password)
return False
How can I log in multiple users in one browser at the same time
By default, the session of the newly logged in user will override the session of the previous user. (in Django_ In the session table, the session of the previously logged in user is overwritten.)
If it is a different browser, it will not be covered. In short, the same browser can only log in to one user at the same time.
On the Internet, there is a way to change the session into a list, but there is no clue at all. I can't help it. Thank you very much.
The answer above got two steps. I should have said something wrong. But I haven't found a solution to this problem for nearly a day. This is my first time to use stack overflow, which is also a memorial. ha-ha. Thank you
I am using django-rest-jwt for authentication in my app.
By default it user username field to autenticate a user but I want let the users login using email or username.
Is there any mean supported by django-rest-jwt to accomplish this.
I know the last option would be write my own login method.
No need to write a custom authentication backend or custom login method.
A Custom Serializer inheriting JSONWebTokenSerializer, renaming the 'username_field' and overriding def validate() method.
This works perfectly for 'username_or_email' and 'password' fields where the user can enter its username or email and get the JSONWebToken for correct credentials.
from rest_framework_jwt.serializers import JSONWebTokenSerializer
from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
User = get_user_model()
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
class CustomJWTSerializer(JSONWebTokenSerializer):
username_field = 'username_or_email'
def validate(self, attrs):
password = attrs.get("password")
user_obj = User.objects.filter(email=attrs.get("username_or_email")).first() or User.objects.filter(username=attrs.get("username_or_email")).first()
if user_obj is not None:
credentials = {
'username':user_obj.username,
'password': password
}
if all(credentials.values()):
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
else:
msg = _('Account with this email/username does not exists')
raise serializers.ValidationError(msg)
In urls.py:
url(r'{Your url name}$', ObtainJSONWebToken.as_view(serializer_class=CustomJWTSerializer)),
Building on top of Shikhar's answer and for anyone coming here looking for a solution for rest_framework_simplejwt (since django-rest-framework-jwt seems to be dead, it's last commit was 2 years ago) like me, here's a general solution that tries to alter as little as possible the original validation from TokenObtainPairSerializer:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomJWTSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
credentials = {
'username': '',
'password': attrs.get("password")
}
# This is answering the original question, but do whatever you need here.
# For example in my case I had to check a different model that stores more user info
# But in the end, you should obtain the username to continue.
user_obj = User.objects.filter(email=attrs.get("username")).first() or User.objects.filter(username=attrs.get("username")).first()
if user_obj:
credentials['username'] = user_obj.username
return super().validate(credentials)
And in urls.py:
url(r'^token/$', TokenObtainPairView.as_view(serializer_class=CustomJWTSerializer)),
Found out a workaround.
#permission_classes((permissions.AllowAny,))
def signin_jwt_wrapped(request, *args, **kwargs):
request_data = request.data
host = request.get_host()
username_or_email = request_data['username']
if isEmail(username_or_email):
# get the username for this email by model lookup
username = Profile.get_username_from_email(username_or_email)
if username is None:
response_text = {"non_field_errors":["Unable to login with provided credentials."]}
return JSONResponse(response_text, status=status.HTTP_400_BAD_REQUEST)
else:
username = username_or_email
data = {'username': username, 'password':request_data['password']}
headers = {'content-type': 'application/json'}
url = 'http://' + host + '/user/signin_jwt/'
response = requests.post(url,data=dumps(data), headers=headers)
return JSONResponse(loads(response.text), status=response.status_code)
I check that whether the text that I received is a username or an email.
If email then I lookup the username for that and then just pass that to /signin_jwt/
authentication.py
from django.contrib.auth.models import User
class CustomAuthBackend(object):
"""
This class does the athentication-
using the user's email address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'app_name.authentication.CustomAuthBackend',
]
How it works:
If user try to authenticate using their username django will look at the ModelBackend class. However, if the user adds its email instead, django will try ModelBackend but will not find the logic needed, then will try the CustomAuthBackend class making it work the authentication.
Alternatively, this new DRF Auth project dj-rest-auth seems to provide support for log in by username or email through djangorestframework-simplejwt.
dj-rest-auth works better for authentication and authorization. By default dj-rest-auth provides - username, email and password fields for login. User can provide email and password or username and password. Token will be generated, if the provided values are valid.
If you need to edit these login form, extend LoginSerializer and modify fields. Later make sure to add new custom serializer to settings.py.
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'yourapp.customlogin_serializers.CustomLoginSerializer'
}
Configuring dj-rest-auth is bit tricky, since it has an open issue related to the refresh token pending. There is workaround suggested for that issue, so you can follow below links and have it configured.
https://medium.com/geekculture/jwt-authentication-in-django-part-1-implementing-the-backend-b7c58ab9431b
https://github.com/iMerica/dj-rest-auth/issues/97
If you use the rest_framework_simplejwt this is a simple mode. views.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.http import JsonResponse
from rest_framework import generics
class EmailAuthToken(generics.GenericAPIView):
def post(self, request):
user_data = request.data
try:
user = authenticate(request, username=user_data['username_or_email'], password=user_data['password'])
if user is not None:
login(request, user)
refresh = RefreshToken.for_user(user)
return JsonResponse({
'refresh': str(refresh),
'access': str(refresh.access_token),
}, safe=False, status=status.HTTP_200_OK)
else:
return JsonResponse({
"detail": "No active account found with the given credentials"
}, safe=False, status=status.HTTP_200_OK)
except:
return Response({'error': 'The format of the information is not valid'}, status=status.HTTP_401_UNAUTHORIZED)
I want to use Tastypie's ApiKeyAuthentication to authenticate a request and then establish a session for the user within a Django view. I have username and api_key for the user. I do not have the user's password. This is the code I currently have:
class ApiKeyPlusWebAuthentication(ApiKeyAuthentication):
def is_authenticated(self, request, **kwargs):
isAuthenticated = super(ApiKeyPlusWebAuthentication, self).is_authenticated(request, **kwargs)
if isAuthenticated:
print request.user.email
return isAuthenticated
#login for access from UIWebView
def login_usingApiKeyAuthentication(request):
auth = ApiKeyPlusWebAuthentication(request)
if auth.is_authenticated(request):
print 'authenticated'
login(request, request.user)
return redirect(reverse(view_name))
else:
print 'NOT authenticated'
messages.error(request, MESSAGE_INVALID_LOGIN)
fail_redirect = redirect(reverse('login'))
return fail_redirect
I am getting an error 'User' object has no attribute 'backend'. This is because I haven't called authenticate(user, password). I am using the Django default authentication backend.
In this scenario, I only have APIKey associated with the user and don't have the raw password for authentication.
One way to handle this may be to create custom authentication backend that bypasses password requirement. However, registering a "password-less" authentication backend in settings.py seems like a hack prone to security breakdown.
So, how can I use ApiKeyAuthentication and then authenticate & login the user in Django establishing a session?
I found a solution to set the backend in another post. You can set the custom backend directly on the user object.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#csrf_exempt
def login_uiwebview(request):
auth = ApiKeyPlusWebAuthentication(request)
if auth.is_authenticated(request):
view_name = request.POST.get('view_name')
request.user.backend = 'app.views.PasswordlessAuthBackend'
login(request, request.user)
return redirect(view_name)
else:
print 'NOT authenticated'
messages.error(request, MESSAGE_INVALID_LOGIN)
fail_redirect = redirect(reverse('login'))
return fail_redirect
When do sessions get created and destroyed?
In my application I have
def app_login(request):
request.session.set_expiry(0)
if 'current_day' not in request.session:
request.session['current_day'] = Utilities.default_day()
Then further down I use :
login(request, user)
If I login in as a user, this works fine and 'current_day' is retained in session. But if I log out as that user and log in as another, then 'current_day' is lost and is not available immediately after calling login().
I assume that
logout(request)
does not clear the session, and when the second user tries to login the data 'current_'day' is still available in the session but calling login(user) presumably creates a new session.
Is this assumption correct and how best to correct this?
login source:
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.pk:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.pk
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
Anonymous sessions are retained (they don't have a SESSION_KEY), relogin as a different user flushes session.
Logout also flushes the session:
def logout(request):
"""
Removes the authenticated user's ID from the request and flushes their
session data.
"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, 'user', None)
if hasattr(user, 'is_authenticated') and not user.is_authenticated():
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
request.session.flush()
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
These are the only two cases when session is flushed.
You should set current_day after logging in (or check its existence on every request with a custom middleware).
I'm using celery and would like to drive sessions on behalf of the user who submitted the request rather than the "root" user. For example a basic task looks like this (a very contrived example)
#task
def process_checklist(**kwargs):
log = process_checklist.get_logger()
document = kwargs.get('document', None)
company = kwargs.get('company', None)
user = kwargs.get('user', None)
object = Book.object.get_or_create(name=kwargs.get('name'))
There are tradeoffs to doing this but I feel it would be far more beneficial to actually use the views to do this, very similar to how we test things.. Practially speaking this is used for batch uploading of data, where each row is effectively a CreateView.
client = Client()
client.login(user='foo', password='bar')
client.post(reverse('create_book_view', data=**kwargs))
But I can't think of a good way to practically use (if it's possible) the django.test.client Client class to log a user in without knowing the password and fill in a view for them. I thought of this but I'm sure there is a better way??
Here is what I came up with?
class AxisClient(Client):
# The admin account is created by us. Once that is done everything should be tested through
# the system.
def login_user(self, username):
"""
Sets the Factory to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
user = User.objects.get(username=username)
user.backend = None
if user and user.is_active \
and 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = import_module(settings.SESSION_ENGINE)
# Create a fake request to store login details.
request = HttpRequest()
if self.session:
request.session = self.session
else:
request.session = engine.SessionStore()
login(request, user)
# Save the session values.
request.session.save()
# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.cookies[session_cookie].update(cookie_data)
return True
else:
return False
This appears to work!
class AxisClient(Client):
# The admin account is created by us. Once that is done everything should be tested through
# the system.
def login_user(self, username):
"""
Sets the Factory to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
user = User.objects.get(username=username)
user.backend = None
if user and user.is_active \
and 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = import_module(settings.SESSION_ENGINE)
# Create a fake request to store login details.
request = HttpRequest()
if self.session:
request.session = self.session
else:
request.session = engine.SessionStore()
login(request, user)
# Save the session values.
request.session.save()
# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.cookies[session_cookie].update(cookie_data)
return True
else:
return False