I'm using django for my API and react for my frontend app. My problem is I do not know how to get csrf token which is needed to submit the login form (I do not need registrition form, just few users). This is the code for handling the /accounts/login :
from django.contrib.auth import authenticate, login
from django.http import JsonResponse
from json import loads
def login_user(request):
body_unicode = request.body.decode('utf-8')
body = loads(body_unicode)
username = body['username']
pwd = body['pwd']
user = authenticate(request, username=username, password=pwd)
try:
if user.is_authenticated:
login(request, user)
email = User.objects.get(username=username).email
return JsonResponse({"username":username, \
"pwd":pwd, \
"email":email }, \
status=200)`
except Exception as expt:
return JsonResponse({"error": str(expt)}, status=401)
And in my react app I'm trying to make a request for logging /accounts/login using the X-CSRFToken header and the csrf token goten by the getCookie() function (found here), but it is always null and the response always rejected with 403 status code.
Could you please show me how I can handle that situation please ? (I do not want to use csrf_exempt which pose security issues).
Related
I am using DRF with JWT (JSON Web Token) authentication. I want to use django's built-in reset password functionality. So, I have included the urls:
url('^', include('django.contrib.auth.urls')),
But, of course, under an API, calling https://.../password_reset/ results on a csrf token missing error. I am wondering which aproach should I take to solve this. Should I change the built in reset_password view and remove csrf protection? Is it a better idea to create a DRF endpoint that accepts the email (reset_password view post parameter) and then somehow generate a csrf token and send it to the view with redirect(reverse("reset_password"), email=email) ... but then, redirect will not send a post request to the reset_password view. Maybe saving the email to session? Any advice will help.
I think in the case of a password reset endpoint it is safe to remove CSRF protection. CSRF protection is for authenticated endpoints to prevent other websites from using a user's stored credentials to gain unauthorized access. Since the PasswordResetForm used by Django doesn't do anything other than send an e-mail, an attacker can't really do much other than annoy a user by spamming them with password reset emails.
You could use a third party for this, but if all you're doing is adding a password reset endpoint, you just need a few lines of code.
views.py
import json
from django.contrib.auth.forms import PasswordResetForm
from django.http.response import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
#csrf_exempt
#require_http_methods(['POST'])
def email_password_reset(request):
# the POST body should be in the format {'email': 'user#user.com'}
try:
# you could also uncomment the following line if you wanted this view to be anonymous only
# assert not request.user.is_authenticated()
assert request.META.get('CONTENT_TYPE', '') == 'application/json'
body = json.loads(request.body)
except (AssertionError, TypeError):
pass
else:
form = PasswordResetForm(body)
if form.is_valid():
form.save()
finally:
return HttpResponse(status=200)
urls.py
urlpatterns = patterns(...
url(r'^/api/password_reset/$', 'email_password_reset', name='email-password-reset')
...)
I use Django 1.9.7 & Python 3.5
I implement creating user mechanism and tried to test with POSTMAN(chrome application), but it doesn't work and it shows something like belows:
Forbidden (CSRF cookie not set.): /timeline/user/create/
This is the code :
urls.py
from django.conf.urls import url
From. import views
app_name = 'timeline'
urlpatterns = [
# ex) /
url(r'^$', views.timeline_view, name='timeline_view'),
# ex) /user/create
url(r'^user/(?P<method>create)/$', views.user_view, name='user_view'),
]
views.py
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, HttpResponse
from timeline.models import *
def timeline_view(request):
return HttpResponse('hello world')
def user_view(request, method):
if method == 'create' and request.method == 'POST':
print("hi")
username = request.POST.get('username')
username = request.POST.get('username')
user = User.objects.create_user(username, password=password)
user.first_name = request.POST.get('name','')
user.save()
profile = UserProfile()
profile.user = user
profile.save()
return HttpResponse('create success')
else:
return HttpResponse('bad request', status=400)
POSTMAN :
I tried Django CSRF Cookie Not Set but I think this post is for past version.
for testing i used the #csrf_exempt decorator.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def user_view(request, method):
...
now you should be able to call this function without the csrf cookie.
(last time i tried it, i was using django 1.8.7)
source:
https://docs.djangoproject.com/en/1.9/ref/csrf/#edge-cases
You should put CSRFToken in request headers.
After sending request via postman, look at the response Cookies section, take csrftoken value and put in Headers section of request, like this:
key:X-CSRFToken
value: jSdh6c3VAHgLShLEyTjH2N957qCILqmb #your token value
Sometimes Version problem in 'Postman' :
I have face the same problem. While sending the data using the oldest version of postman in POST method.
That time I have received the empty json data in server side.
And I have fix this problem, Once I uninstall the oldest version of postman and installed with latest version.
Use this below statement on top of each and every view function definition (views.py). We don't need to use CRF related statements.
#api_view(["POST", "GET"])
eg:
#api_view(["POST", "GET"])
def GivenInput():
return Response(e.args[0],status.HTTP_400_BAD_REQUEST)
Note*:
But I didn't know that any alternative way to make it global throughout the file.
I am attempting to learn Django's authentication system by contriving a basic login scenario. My views are set up such that a view, logIn, either receives a user's credentials (and prints the success/failure of the login), or it renders a login form.
A second view, privatePage, is designed as a sanity check that the user is actually logged in. The code is as follows:
views.py:
#login_required(login_url='/logIn')
def privatePage(request):
return HttpResponse("You're viewing a private page")
#csrf_exempt
def logIn(request):
if request.method == "POST" and \
request.POST.get('email') and \
request.POST.get('password'):
user = authenticate(username=request.POST['email'],
password=request.POST['password'])
return HttpResponse('Valid login' if user is not None else 'Invalid login')
# render login form
return HttpResponse("<form>...</form>")
I'm finding that after succcessfully logging in via the logIn view, I am still redirected to the login view upon trying to visit privatePage. FYI, I'm attempting to visit the privatePage view directly by URL, as opposed to navigating through provided links (e.g. I'm not sure if I'm violating some CSRF rule).
Any idea what's going on?
You've not actually logged in. You need to login the user after verifying their identity with authenticate:
from django.contrib.auth import login
user = authenticate(email=email, password=password)
if user is not None:
login(request, user)
login should only be used on users that have been confirmed to exist.
What authenticate does:
verifies a user is who they claim to be
It does not perform the actual login.
To keep the user logged in a session must be provided to user with usage of login() method. Login is the process of providing user with a session and authenticate() verifies that the given credentials corresponds to an existing user model object in database . Import django's built in login and authenticate methods from django.contrib.auth import authenticate, login. And then your code looks like
user =authenticate(email, password)
If user:
login(user, request)
Hope it helps :)
I am using the OAUTH API to verify access to the users google calendar. They are not logging into my system using OAUTH, just accepting my site accessing their data.
The problem is, if the user is logged out of their google account and after they hit verify, it force logs them out of their session and I have no way of linking them back up.
This DOES work if they are already logged into the google account in their browser session and hit accept, they will be redirected to the right page.
I replicate the error when the cache, cookies are clear and they need to relogin into their google account to verify.
I've tried storing the session ID, etc, but the request parameter is not containing the same request data as the initial view, so there is a conflict in the data I am trying to retrieve.
The user is logged in using the standard Django libraries for the credentials model.
CODE
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='http://127.0.0.1:8000/oauth2callback')
'''''''''''''''''''''''''''''''''''''''''''''''''''
Main function dealing with auth verification
'''''''''''''''''''''''''''''''''''''''''''''''''''
def index(request):
current_user = User.objects.get(username=request.user.username)
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
request.user.id)
authorize_url = FLOW.step1_get_authorize_url()
return redirect(authorize_url)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
User then calls the data function once authenticated
''''''''''''''''''''''''''''''''''''''
def auth_return(request):
print("THE CURRENTLY REQUESTED USER IN THIS SESSION REQUEST IS %s"%(request.user.username))
credential = FLOW.step2_exchange(request.REQUEST)
try:
current_user = User.objects.get(id=request.user.id)
except:
return HttpResponseRedirect("/login")
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
storage.put(credential)
return HttpResponseRedirect("/get_cal")
Ok, so this was a bit more involved than I thought.
I fixed this by adding a state parameter of the currently logged in username.
logged_in_username = request.user.username
user_data = {'var_test' : logged_in_username}
pass_param = urllib.urlencode(user_data)
FLOW.params['state']=pass_param
authorize_url = FLOW.step1_get_authorize_url()
This gave me the ability to query the user from the DB via the User model in Django contrib. I parsed out the state var from the URL:
#Get the currently logged in username
user_variable_data = str(FLOW.params['state'])
#get rid of the var_test= preprended text data for parsing reasons
user_variable_data = user_variable_data[9:]
#Get that user from the database in the form of a user object
current_user = User.objects.get(username=user_variable_data)
and then built a custom backend authentication file to auth the user without a password to maintain the request like nothing weird ever even happened.
user = authenticate(username=user_variable_data)
login(request, user)
print("AUTHENTICATED")
Appended this to settings file
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'procrastinate.auth_backend.PasswordlessAuthBackend',
)
Custom Backend File
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
I found this snippet of code that helps me authenticate a user and then create a rest_framework token for them. The client I am using is a native android app and I will get the access token from the client side and post it to django in the ObtainAuth class.
Here is the code for the server side.
#psa('social:complete')
def register_by_access_token(request, backend):
backend = request.strategy.backend
# Split by spaces and get the array
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
msg = 'No token header provided.'
return msg
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
return msg
access_token = auth[1]
user = backend.do_auth(access_token)
return user
class ObtainAuthToken(APIView):
model = Token
serializer_class = AuthTokenSerializer
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
def post(self,request):
serializer = self.serializer_class(data= request.DATA)
if backend == 'auth':
if serializer.is_valid:
token, created = Token.objects.get_or_create(user=serializer.object['user'])
if token:
return Response({'token': token.key})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
user = register_by_access_token(request, backend)
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'email': user.email, 'firstname': user.first_name, 'userRole': 'user', 'token': token.key})
The register_by_access_token method will get the facebook access token and then create a user with the rest_framework.It takes a request and the backend to be used e.g 'facebook'.
If a user logs in with my backend then the backend is 'auth' and it uses the normal process of retrieving the email and password and then giving me a token to use.As detailed here
My question is how do I post the authentication backend be it 'facebook' or 'auth' so that I can receive the token?
What I've tried.
I have tried sending the backend type ('facebook' or 'auth') with the access token but I get an error that the method takes 3 arguments and I've only provided 2.
I've tried making the url take a backend like this:
url(r'^login/(?P<backend>[^/]+)/$',views.ObtainAuthToken.as_view())
then sending the access token to a url like this mysite.com:8000/login/facebook.
None of these work and I don't have much expereience with psa or django to know how to pass this parameter.
How do I send which backend to use so that it can be accepted by the method? If anyone has ever had this use case please help me out.
according to my understanding social login requires a access token , so when you are login with facebook when you call 'mysite.com:8000/login/facebook' it is expecting a access token,
for my project i defined my urls as 'url(r'^login/(?P[^/]+)/$',register_by_access_token,name='register_by_access_token')',with the token i am sending it completes the login, for facebook i send backend as 'facebook' for google i send backend as 'google-oauth2' but both the case i am sending a token given my respective backend, when you are not using a third party backend you wont get the token and the login expects that.
so my suggestion is if you are going with auth use normal login post, not the same URL.