i'm writing a django / angularjs application, and i'm trying to use the #permission_required for user permission authorization.
I'm returning from the client side both headers- sessionId and csrf token, and yet the #permission_required method user, is anonymousUser, although when i'm logging in the user, i use - login(request, user) method, and the user arg is updated to the current user:
#api_view(['GET', 'POST', 'DELETE'])
def log_in_view(request):
body = request.body
json_body = json.loads(body)
email = json_body.get("email")
password = json_body.get('password')
user = authenticate(email=email, password=password)
session = request.session
if user is not None:
request.session['email'] = email
request.session['password'] = password
session.set_expiry(900)
session.save()
session_key = session.session_key
login(request, user)
crcf = get_token(request)
response_body = {}
response_body.update({"session_key" : session_key})
response_body.update({"csrf" : crcf})
return HttpResponse(json.dumps(response_body), content_type="text/json", status=200)
else:
return HttpResponse("could not authenticate user", content_type="text/plain", status=401)
does anyone have any idea what am i doing wrong?
cheers
you dont need all those stuff to login the user. I mean you dont need to set up session manually, either return csrf token.
#api_view(['GET', 'POST', 'DELETE'])
def log_in_view(request):
body = request.body
json_body = json.loads(body)
email = json_body.get("email")
password = json_body.get('password')
user = authenticate(email=email, password=password)
if user:
login(request, user)
response_body = {}
response_body['user'] = user
return HttpResponse(json.dumps(response_body), content_type="text/json", status=200)
else:
return HttpResponse("could not authenticate user", content_type="text/plain", status=401)
By use of #api_view I'm assuming that you're using Django REST Framework.
Each api view of django rest framework don't rely by default on django authentication. If you want your view to use django authentication, you should add proper #authentication_classes(...) decorator to your view or specify it globally in your settings.py file.
Related
Project's backend and frontend are separate and trying to implement provider login via google.
in settings.py
LOGIN_REDIRECT_URL = "http://localhost:3000"
SOCIALACCOUNT_ADAPTER = "users.adapter.CustomOAuth2Adapter"
in adapter.py
class CustomOAuth2Adapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form):
user = sociallogin.user
user.is_active = True
user.save()
token = Token.objects.create(user=user)
response = HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
response.set_cookie('auth_token', token)
return response
def pre_social_login(self, request, sociallogin):
try:
user = User.objects.get(email=sociallogin.user.email)
user.is_active = True
user.save()
request.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
# sociallogin.connect(request, user)
# return response
except:
pass
there is one main problem here. when login or save has success trying to redirect from backend to frontend. It's using LOGIN_REDIRECT_URL and works. but when it's redirecting I try to set token into cookie. but it doesn't set.
request.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
Additionally I tried to set cookie like below.
def pre_social_login(self, request, sociallogin):
try:
user = User.objects.get(email=sociallogin.user.email)
user.is_active = True
user.save()
# sociallogin.connect(request, user)
response = HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
response.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
return response
except:
pass
but this doesn't work either. when backend and frontend are separate how to send token in redirection response.
within an application, a user with an administrator role, through a DRF endpoint, is able to create new user accounts.
The need is to automatically send the password reset link to the emails of the newly created users.
I have defined an url:
path('v1/account/register/',
AccountCreationView.as_view(),
name='custom_account_creation'),
the view that first of all check that user role allow the creations of new users:
class AccountCreationView(RegisterView):
"""
Accounts Creation
"""
serializer_class = RegisterWithMailSendSerializer
def get_response_data(self, user):
# print('get_response_data', user)
self.user = user
def create(self, request, *args, **kwargs):
role_section = 'UsersAdmins'
#
rights_check = role_rights_check(
request.user,
role_section,
"R",
)
if rights_check[0] == False:
return Response({"error": rights_check[1]},
status=status.HTTP_401_UNAUTHORIZED)
response = super().create(request, *args, **kwargs)
and a custom serializer for that views, where after validating data, save and then create the password reset link and send via email to the newly created user:
class RegisterWithMailSendSerializer(RegisterSerializer):
def save(self, request, **kwargs):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user = adapter.save_user(request, user, self, commit=False)
if "password1" in self.cleaned_data:
try:
adapter.clean_password(self.cleaned_data['password1'],
user=user)
except DjangoValidationError as exc:
raise serializers.ValidationError(
detail=serializers.as_serializer_error(exc))
user.save()
self.custom_signup(request, user)
setup_user_email(request, user, [])
pg = PasswordResetTokenGenerator()
pg_token = pg.make_token(user)
print('>>> pg_token', pg_token)
frontend_site = settings.FRONTEND_APP_BASE_URL
token_generator = kwargs.get('token_generator',
default_token_generator)
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
email = self.get_cleaned_data()['email']
get_adapter(request).send_mail('password_reset_key', email, context)
return user
in settings.py
CSRF_COOKIE_SECURE isn't set and has it's default False value.
everything seems to work, the user is created and the link with uid and token is sent to the relative email BUT the token seem is invalid when the user tries to reset his password...
Printed 'pg_token' is the same founded into the sended URL.
For completeness here the custom serializer used to reset the password:
in settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'api.serializers.serializers_auth.CustomPasswordResetSerializer',
'TOKEN_SERIALIZER': 'api.serializers.serializers_auth.TokenSerializer',
}
serializers_auth.py
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def save(self, request, **kwargs):
frontend_site = settings.FRONTEND_APP_BASE_URL
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator',
default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail('password_reset_key', email,
context)
return self.cleaned_data['email']
class CustomPasswordResetSerializer(PasswordResetSerializer):
#property
def password_reset_form_class(self):
return CustomAllAuthPasswordResetForm
I tried everything, including the same calls for creation and reset through Postman thinking that, for some reason, the token was invalidated by the automatic login in the DRF web interface after the user was created but I don't understand why the token is not valid.
If i try manually POST email address on /api/v1/auth/password/reset/ and then use provided uid/token on /api/v1/auth/password/reset/confirm/ the password reset works as expected.
Some experience and tips are really appreciated.
you could easily implement full user authentication with Django Djoser
Check the docs: https://djoser.readthedocs.io/en/latest/getting_started.html
Available endpoints
/users/
/users/me/
/users/confirm/
/users/resend_activation/
/users/set_password/
/users/reset_password/
/users/reset_password_confirm/
/users/set_username/
/users/reset_username/
/users/reset_username_confirm/
/token/login/ (Token Based Authentication)
/token/logout/ (Token Based Authentication)
/jwt/create/ (JSON Web Token Authentication)
/jwt/refresh/ (JSON Web Token Authentication)
/jwt/verify/ (JSON Web Token Authentication)
Solved by calling password reset endpoint with email parameter immediately after the user is created, without any custom logic or overrides:
from rest_framework.test import APIClient
if settings.SEND_EMAIL_PWD_CHANGE_TO_NEW_USERS == True:
client = APIClient()
client.post('/api/v1/auth/password/reset/', {'email': user.email}, format='json')
And now the email with the reset link contain a valid token for the password reset.
Hey everyone I have a couple questions in regards to refactoring some old api endpoints as far as authentication goes. I have a view for example...
#csrf_exempt
# PARAMETERS: username, password
def submit_offer(request):
"""Submit an offer"""
username = request.GET.get("username")
password = request.GET.get("password")
# Authenticate user to set instance.user value into BuyerForm
user = authenticate(username=username, password=password)
if not user:
# Always want our potential Buyer to be logged in & authenticated
return JsonResponse({'message': 'Please login to continue.'})
if request.method == 'POST':
form = BuyerForm(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
# sets current user as Buyer.user
instance.user = user
instance.save()
return JsonResponse({'success': True}, status=200)
else:
data = form.errors.as_json()
return JsonResponse(data, status=400, safe=False)
else:
return JsonResponse(data={'status': 403})
Now every view that uses a form, and needs to grab the instance.user, has the same lines of code below...now I thought using request.user would do the job, but when testing that way I am getting back an AnonymousUser, which is kind of confusing me?
username = request.GET.get("username")
password = request.GET.get("password")
# Authenticate user to set instance.user value into BuyerForm
user = authenticate(username=username, password=password)
Now is there a better way to authenticate the user, like in a regular django view using request.user, rather than having to manually authenticate the user in each view? (edited)
password = request.GET.get("password").
This is very vulnerable way to design a django app.
Please see
Accessing Username and Password in django request header returns None
BTW, write a custom middle ware and put your code there.
username = get_username_from_header
password = get_password_from_header
# Authenticate user to set instance.user value into BuyerForm
user = authenticate(username=username, password=password)
# Attach user to request
request.user = user
As each request are being passed through the middle-ware, you can access the user from each view.
I have a backend in Django +allauth, rest framework and native app in cordova/jquery mobile. I have to access login/logout page in this native app.
I'm a little confused with that.
I make simple ajax to test it.
I added
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
$.ajax({
type : "POST",
url : url+"login/",
data : {x:'user1', y:"pass1"}
})
I created in my django db this model (user1, pass1)
And I have view in Django
#api_view(['POST'])
def login(request):
username = request.POST.get('x')
password = request.POST.get('y')
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(user)
return Response({...})
But in line
login(user)
I get this error:
AttributeError: 'User' object has no attribute 'encoding'
When I put print after
user = authenticate(username=username, password=password)
print user
I get this user "user1"
Dou you guys have anu ideas how correctly make this login system?
I figured out. Just rename function to something other than 'login'.
Since I believe you are using Django Rest Framework, here is a class based function to login (ignore the token side if you want, but I am assuming you are also using tokens or something like that.
class APILoginViewSet(APIView):
"""
View to list all users in the system.
* Requires token authentication.
"""
#csrf_exempt
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = LoginCustomSerializer(data=data)
if serializer.is_valid():
email = serializer.data.get('email')
password = serializer.data.get('password')
if not request.user.is_anonymous():
return Response('Already Logged-in', status=status.HTTP_403_FORBIDDEN)
account = authenticate(email=email, password=password)
if account is not None:
if account.is_active:
login(request, account)
# Add the token to the return serialization
try:
token = Token.objects.get(user=account)
except:
token = Token.objects.create(user=account)
data = {
'token': token.key
}
return Response(data)
else:
return Response('This account is not Active.', status=status.HTTP_401_UNAUTHORIZED)
else:
return Response('Username/password combination invalid.', status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and in your serializer.py
class LoginCustomSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=200)
password = serializers.CharField(max_length=200)
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)