I use the Django framework as backend and vue.js and webpack for the frontend. to implement authentication, send a request via ajax to Django and want to keep session for next requests but can not keep session.
I use the request node module for send request and use the jar to keep session but get error 403 again. it means jar does not keep the session for this user.
in vuejs:
var request = require('request');
get_jar(){
if(this.jar){
return this.jar;
}
this.jar = request.jar();
return this.jar;
}
send_ajax(url, data, method, callback){
request(
{
url: url,
method: method,
form: data,
jar: this.get_jar(),
},
function(err, res, body){
callback(err, res, body)
});
}
in django:
#method_decorator(csrf_exempt, 'dispatch')
class Login(View):
#staticmethod
def post(request):
email = request.POST.get('email', None)
password = request.POST.get('password', None)
user = authenticate(request, username=email, password=password)
if user is None:
return JsonResponse({'message': 'incorrect username or password'}, status=403)
else:
login(request, user)
return JsonResponse({'message': 'user logged in'})
a decorator for check user log in:
def user_login_required(method):
#wraps(method)
def wrap(request, *args, **kwargs):
if request.user.is_authenticated:
return method(request, *args, **kwargs)
else:
return JsonResponse({'message': 'user not recognized'}, status=403)
return wrap
can somebody help me, please?
Related
Django v1.10
FormView code:
class PasswordResetConfirmView(FormView):
template_name = "dashboard/account/reset_password_form.html"
success_url = '/dashboard/'
form_class = SetPasswordForm
def authenticate_password_token(self, request, uidb64=None, token=None, encodedtimestring=None):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
timestring = force_text(urlsafe_base64_decode(encodedtimestring))
timestamp = timeparse(timestring)
timediff = timezone.now() - timestamp
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
timediff = None
if timediff is None or timediff.days < 0 or timediff.days > PASSWORD_RESET_TIMEOUT_DAYS:
messages.error(request, _(
'The reset password link is no longer valid.'))
return None
if user is None or not default_token_generator.check_token(user, token):
messages.error(request, _('The reset password link is not valid.'))
return None
return user
def get(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class()
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class(request.POST)
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
if not form.is_valid():
return self.form_invalid(form)
new_password = form.cleaned_data['new_password2']
try:
with transaction.atomic():
user.auth_token.delete()
Token.objects.create(user=user)
user.set_password(new_password)
user.save()
except:
messages.error(request, _('Password reset was unsuccessful.'))
return redirect(reverse('dashboard-login'))
messages.success(request, _('Password has been reset.'))
return redirect(reverse('dashboard-login'))
urls.py:
url(r'^(?i)recover/password/(?P<uidb64>[0-9A-Za-z]+)/(?P<token>.+)/(?P<encodedtimestring>.+)/$',
views.PasswordResetConfirmView.as_view(), name='reset-password-confirm'),
testclass parent:
class BaseApiTest(TestCase):
def setUp(self):
superuser = User.objects.create_superuser(
'test', 'test#api.com', 'testpassword')
self.factory = RequestFactory()
self.user = superuser
self.client.login(username=superuser.username, password='testpassword')
My attempt at writing the test case:
class ResetPasswordEmailTest(BaseApiTest):
def test_password_reset_form(self):
"""
Ensure that the authenticate token works
"""
self.client.logout()
token = default_token_generator.make_token(self.user)
uidb64 = force_bytes(self.user.id)
timenow = force_bytes(timezone.now())
response = self.client.get(
reverse('reset-password-confirm',
args=[urlsafe_base64_encode(uidb64), token,
urlsafe_base64_encode(timenow)]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
Error message that I got:
tests/password_tests.py", line 129, in test_password_reset_form
self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 302 != 200
Am not sure how to write a test class to test all 3 methods of the formview. My attempt was just to test the get method
UPDATE:
The real reason for the failure has nothing to do with the user login but somehow the check_token method inherent in the PasswordTokenGenerator was failing my tests.
And as I do more research, I think it would be better that I upgrade Django v1.10 to v1.11 where I need to rewrite this whole thing which may end up invalidating the need for this question.
Your get method of PasswordResetConfirmView is responding with 2 different responses.
Redirect
When user is None it is responding with a Redirect URI
if user is None:
return redirect(reverse('dashboard-login'))
So in that case the status code of response will be HTTP 302 means
that you are redirected to some other URL.
Render to response
When there is user, then the information returned with the response is the method used in the request.
return self.render_to_response(self.get_context_data(form=form))
So in that case the status code of response will be HTTP 200 means
the request has succeeded.
Solution:
In order to make your test case pass you can use assertRedirects
def test_password_reset_form(self):
...
# A URL that redirects can be followed to termination.
response = self.client.get(reverse('reset-password-confirm', args=[...]), follow=True)
self.assertRedirects(response, reverse('dashboard-login'), status_code=302, target_status_code=200)
self.assertEqual(len(response.redirect_chain), 2)
If your request used the follow argument, the expected_url and target_status_code will be the url and status code for the final point of the redirect chain.
Reference:
https://github.com/django/django/blob/master/tests/test_client/tests.py
Alternatively you can create two separate test cases (when user is logged-in, when user is not logged-in) and use assertEqual
# Test for logged in user.
def test_password_reset_form_for_logged_in_user(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test if user not logged in.
def test_password_reset_form_if_user_not_logged_in(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
Problem (Post Mortem):
Let's start with defining the problem:
Status 302:
The requested resource resides temporarily under a different URI. -source
So that leads us to assume that you are getting redirected thus the test fails.
Where this happens:
In your get() method we find this code:
if user is None:
return redirect(reverse('dashboard-login'))
So if the user is Anonymous (Unauthenticated) the view redirects him to the login page.
From the above, we can state that the problem occurs when you make an unauthenticated request to PasswordResetConfirmView.get().
Solution:
You need to authenticate (login) your user before you make the request.
You can do this by utilizing the force_login() method:
If your site uses Django’s authentication system, you can use the force_login() method to simulate the effect of a user logging into the site. Use this method instead of login() when a test requires a user be logged in and the details of how a user logged in aren’t important.
def my_test_case():
... Do stuff ...
# Login
self.client.force_login(self.user)
# Make the request
response = self.client.get(...)
It had a permission below when my server was cookie-based authentication.
class IsAuthenticatedAndStudentOwner(BasePermission):
message = 'You must be a student.'
def has_permission(self, request, view):
return request.user.is_authenticated() and smart_str(request.user.identity) == 'student'
def has_object_permission(self, request, view, obj):
return obj.student.user == request.user
Then, when i use JWT, that request.user returns AnonymousUser.
# login(request, user_obj)
payload = jwt_payload_handler(user_obj)
token = jwt_encode_handler(payload)
data['token'] = token
return data
So, how can i write this permission without session?
I'm building simple API with Django REST Framework, everything works as expected with curl and API web admin, but if I run the following test:
class OrderTest(APITestCase):
username = 'admin'
password = '12345'
def setUp(self):
User.objects.create(
username=self.username,
password=self.password,
email='demo#demo.com',
is_superuser=True,
is_staff=True
)
def test_create_order_by_admin(self):
url = '/api/orders/'
data = {
'name': 'John Doe',
'phone': '380000000000',
'status': 1,
'email': 'jonhn.doe#gmail.com',
'date': datetime.now(),
}
# Cheking if user exist
self.assertEqual(User.objects.get(pk=1).username, self.username)
self.client.login(
username=self.username,
password=self.password,
)
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Order.objects.count(), 1)
for key, value in data.items():
self.assertEqual(Order.objects.get().key, value)
it fails with the following error:
Failure
Traceback (most recent call last):
File "/home/linevich/projects/swebion.com/project/order_form/tests.py", line 71, in test_create_order_by_admin
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 403 != 201
That means that client.login() not working.
Any ideas?
You shouldn't set the password directly. This will store password in clear text while Django will try a hashing algorithm when trying to login.
See set_password to store it encrypted.
Use authenticate function in place of client.login
Here i use generic class based view to login user..
class Login(generics.CreateAPIView):
'''
API for SignIn to return User models along with access token.
'''
def post(self, request, *args, **kwargs):
username = self.request.data.get('email', None)
password = self.request.data.get('password', None)
user = authenticate(username=username, password=password)
response = {}
if user:
access = AppCustomMethods()
access_token = access.create_access_token(user, request,
settings.XAMARIN_APPLICATION_NAME)
response_data = {}
response_data['access_token'] = access_token
signup_serializer = serializers.GetUserWithAllBusinesses(user)
response_data[settings.USER] = signup_serializer.data
response = GetAccesUtility.data_wrapper(response_data)
return Response(response, status=status.HTTP_200_OK)
else:
response['error'] = Messages.NOT_AUTHENTICATED # error_data
response["status_code"] = settings.HTTP_USER_ERROR
return Response(response)
user = authenticate(username=username, password=password)
Django authenticate function authenticate the user username or password and return user info. If it return user then a new access token is generated and return in response with status 200 else if user not return by authenticated function then error message return in response.
Problem was in using User.objects.create() insetad of User.objects.create_superuser(), thanks to #C14L.
In my django app, I have written a custom authentication backend following the example in the docs that appears to be working fine. (I email users a login url containing a signed token; the view below handles these token login urls.) I can confirm from the log messages that request.user is being set correctly.
However, the profile view, protected by the #login_required decorator, redirects to the login page as if the user were not logged in. It seems like the call to login is not working. Am I overlooking something? I'm not sure how to debug this further. (As this is my first django app, I decided to write my own login view, and that's working fine...) Thanks!
From views.py:
def token_login(request, token):
user = authenticate(token=token)
if user:
log.info("LOGGING IN {}".format(user))
login(request, user)
log.info(request.user)
return redirect('myapp:profile')
else:
return render(request, 'myapp/error.html', {
'title': 'Invalid Token',
'message': 'Sorry, this is not a valid login token.'
})
Custom Authentication Backend:
class TokenBackend:
"""
Authenticates against a signed token; creates a new user if none existed before.
"""
def get_user(self, username):
try:
User.objects.get(pk=username)
except User.DoesNotExist:
return None
def authenticate(self, token):
log.info("AUTHENTICATING WITH TOKENBACKEND")
try:
token_contents = signing.loads(token, salt=settings.SALT,
max_age=settings.PASSWORD_TOKEN_MAX_AGE)
log.info(token_contents)
except signing.BadSignature:
return None
try:
user = User.objects.get(email=token_contents['email'])
except User.DoesNotExist:
user = User(
username=self.username_from_email(token_contents['email']),
password=self.random_password()
)
user.save()
return user
def random_password(self):
return ''.join(random.choice(string.ascii_uppercase + string.digits)
for _ in range(10))
def username_from_email(self, email):
return email.split('#')[0]
from settings.py:
AUTHENTICATION_BACKENDS = (
'myapp.auth.TokenBackend',
'django.contrib.auth.backends.ModelBackend'
)
Oops. I wasn't returning the User in TokenBackend.get_user.
I'm building a django app with an API backend(built with DRF) and angularjs client. My goal is to completely decouple the server and client using JWT in place of sessions. I'm attempting to integrate python-social-auth(PSA) with django-rest-framework-jwt(DRFJWT), so my goal is to have an auth flow something to this:
User logs with Email/facebook via angular client -> client posts form to PSA's url -> PSA login/create user ->[!] DRFJWT creates token that it then sends back to client -> client stores token in local storage then uses token each request
[!]: This is currently where I'm struggling. My thinking is that I can modify the do_complete method in PSA like so
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
def do_complete(backend, login, user=None, redirect_name='next',
*args, **kwargs):
# pop redirect value before the session is trashed on login()
data = backend.strategy.request_data()
redirect_value = backend.strategy.session_get(redirect_name, '') or \
data.get(redirect_name, '')
is_authenticated = user_is_authenticated(user)
user = is_authenticated and user or None
partial = partial_pipeline_data(backend, user, *args, **kwargs)
if partial:
xargs, xkwargs = partial
user = backend.continue_pipeline(*xargs, **xkwargs)
else:
user = backend.complete(user=user, *args, **kwargs)
if user_is_active(user):
# catch is_new/social_user in case login() resets the instance
is_new = getattr(user, 'is_new', False)
social_user = user.social_user
login(backend, user, social_user)
payload = jwt_payload_handler(user)
return { 'token': jwt_encode_handler(payload) }
Is this the only way of doing what I'm trying to accomplish?
I'm also wondering if its okay from a best-practices standpoint to use sessions to manage the pipeline and JWT for auth?
I'm also using python-social-auth and django-rest-framework-jwt for user authentication.
The way I was able to integrate the two authentication systems together was by creating a custom view that takes in the 'access_token' provided by the oAuth provider and attempts to create a new user with it. Once the user is created, instead of returning the authenticated user/session I return the JWT token.
The following code snippets explain the solution.
Back-End
In my views.py file I included the following:
#psa()
def auth_by_token(request, backend):
"""Decorator that creates/authenticates a user with an access_token"""
token = request.DATA.get('access_token')
user = request.user
user = request.backend.do_auth(
access_token=request.DATA.get('access_token')
)
if user:
return user
else:
return None
class FacebookView(views.APIView):
"""View to authenticate users through Facebook."""
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
auth_token = request.DATA.get('access_token', None)
backend = request.DATA.get('backend', None)
if auth_token and backend:
try:
# Try to authenticate the user using python-social-auth
user = auth_by_token(request, backend)
except Exception,e:
return Response({
'status': 'Bad request',
'message': 'Could not authenticate with the provided token.'
}, status=status.HTTP_400_BAD_REQUEST)
if user:
if not user.is_active:
return Response({
'status': 'Unauthorized',
'message': 'The user account is disabled.'
}, status=status.HTTP_401_UNAUTHORIZED)
# This is the part that differs from the normal python-social-auth implementation.
# Return the JWT instead.
# Get the JWT payload for the user.
payload = jwt_payload_handler(user)
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
# Create the response object with the JWT payload.
response_data = {
'token': jwt_encode_handler(payload)
}
return Response(response_data)
else:
return Response({
'status': 'Bad request',
'message': 'Authentication could not be performed with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
In my urls.py I included the following route:
urlpatterns = patterns('',
...
url(r'^api/v1/auth/facebook/', FacebookView.as_view()),
...
)
Front-End
Now that the backend authentication is wired up, you can use any frontend library to send the access_token and authenticate the user. In my case I used AngularJS.
In a controller file I call the API like so:
/**
* This function gets called after successfully getting the access_token from Facebook's API.
*/
function successLoginFbFn(response) {
var deferred = $q.defer();
$http.post('/api/v1/auth/facebook/', {
"access_token": response.authResponse.accessToken,
"backend": "facebook"
}).success(function(response, status, headers, config) {
// Success
if (response.token) {
// Save the token to localStorage and redirect the user to the front-page.
Authentication.setToken(response.token);
window.location = '/';
}
deferred.resolve(response, status, headers, config);
}).error(function(response, status, headers, config) {
// Error
console.error('Authentication error.');
deferred.reject(response, status, headers, config);
});
}
With this approach you can mix the two plugins. All sent tokens will be coming from django-rest-framework-jwt even though users can still authenticate themselves with the ones provided by sites such as Facebook, Google, Twitter, etc.
I only showed the approach to authenticate through Facebook, however you can follow a similar approach for other providers.
No, you do not need to use sessions(standard Django login system) with python-social-auth. What you need to make JWT and PSA work together is DRF.
Here's my solution:
I used standard PSA's url for making request too social /login/(?P<backend>[^/]+)/$, changed url in urls.py to match redirect from Facebook/Twitter to my own.
url(r'^complete/(?P<backend>[^/]+)/$', views.SocialAuthViewComplete.as_view()),
The point of using API is to have access to user data in request that PSA is doing. DRF allow you to do it if you have JWT authentication in DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),}
In views.py
from social.apps.django_app.views import complete
class SocialAuthViewComplete(APIView):
permission_classes = ()
def post(self, request, backend, *args, **kwargs):
try:
#Wrap up PSA's `complete` method.
authentication = complete(request, backend, *args, **kwargs)
except Exception, e:
exc = {
'error': str(e)
}
return Response(exc, status=status.HTTP_400_BAD_REQUEST)
return Response({'data': authentication}, status=status.HTTP_202_ACCEPTED)
Then I modified the do_complete method in PSA:
def do_complete(backend, login, user=None, redirect_name='next',
*args, **kwargs):
# pop redirect value before the session is trashed on login()
data = backend.strategy.request_data()
redirect_value = backend.strategy.session_get(redirect_name, '') or \
data.get(redirect_name, '')
is_authenticated = user_is_authenticated(user)
user = is_authenticated and user or None
partial = partial_pipeline_data(backend, user, *args, **kwargs)
if partial:
xargs, xkwargs = partial
user = backend.continue_pipeline(*xargs, **xkwargs)
else:
user = backend.complete(user=user, *args, **kwargs)
user_model = backend.strategy.storage.user.user_model()
if user and not isinstance(user, user_model):
return user
if is_authenticated:
if not user:
information = 'setting_url(backend, redirect_value, LOGIN_REDIRECT_URL'
else:
information = 'setting_url(backend, redirect_value, NEW_ASSOCIATION_REDIRECT_URL,LOGIN_REDIRECT_URL'
elif user:
# Get the JWT payload for the user.
payload = jwt_payload_handler(user)
if user_is_active(user):
is_new = getattr(user, 'is_new', False)
if is_new:
information = 'setting_url(backend, NEW_USER_REDIRECT_URL, redirect_value, LOGIN_REDIRECT_URL'
else:
information = 'setting_url(backend, redirect_value, LOGIN_REDIRECT_URL'
else:
return Response({
'status': 'Unauthorized',
'message': 'The user account is disabled.'
}, status=status.HTTP_401_UNAUTHORIZED)
else:
information = 'setting_url(backend, LOGIN_ERROR_URL, LOGIN_URL'
return { 'an information i may use in future': information,
'token': jwt_encode_handler(payload) # Create the response object with the JWT payload.
}
I tried pipelines and user association and it works correctly.
Also you always can modify another method from PSA, if you need it to works with JWT.