CSRF token failing on second post Django Rest Framework - django

I'm having an issue where I can log in successfully but any subsequent requests show as
"detail":"CSRF Failed: CSRF token missing or incorrect."
I have no clue what I'm doing wrong, I've looked over Requests docs, DRF docs, turned off authentication to validate the url and searched old SO posts on the subject.
Here is a basic function with attached basic info
def will_fail():
CURRENT_URL = 'http://127.0.0.1:8000/{}'
session = requests.Session()
response = session.get(CURRENT_URL.format('api-auth/login/'))
csrftoken = response.cookies['csrftoken']
first_response = session.post(CURRENT_URL.format('api-auth/login/'),
data={'username': 'itsme', 'password': 'password'},
headers={'X-CSRFToken': csrftoken})
response = session.post(CURRENT_URL.format('api-v1/languages/'),
params={'name': "French", "audio_base": "adpifajsdpfijsdp"},
headers={'X-CSRFToken': csrftoken})
first_response (login):
URL - 'http://127.0.0.1:8000/api-v1/'
Text - {"languages":"http://127.0.0.1:8000/api-v1/languages/","phrases":"http://127.0.0.1:8000/api-v1/phrases/","stats":"http://127.0.0.1:8000/api-v1/stats/"}
Status - <Response [200]>
response (add language):
URL - 'http://127.0.0.1:8000/api-v1/languages/?audio_base=adpifajsdpfijsdp&name=French'
Text - {"detail":"CSRF Failed: CSRF token missing or incorrect."}
Status - <Response [403]>
The settings are very basic since I'd just started on this:
THIRD_PARTY_APP = [
'rest_framework.authtoken',
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
Here is the URL info which is also pretty generic
from training.views import LanguageViewSet, PhraseViewSet, PhraseStatsViewSet
router = DefaultRouter()
router.register(r'languages', LanguageViewSet)
router.register(r'phrases', PhraseViewSet)
router.register(r'stats', PhraseStatsViewSet)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-v1/', include(router.urls, namespace='api'))
]
I'm using ModelSerializers and ModelViewSets, I didn't override any methods and included all fields.
EDIT:
I had already tried updating the token as well but it gave me a KeyError -
Traceback (most recent call last):
File "C:/Users/Me/PycharmProjects/proj/post_data.py", line 70, in <module>
will_fail()
File "C:/Users/Me/PycharmProjects/proj/post_data.py", line 62, in will_fail
csrftoken = first_response.cookies['csrftoken']
File "C:\Users\Me\Envs\proj\lib\site-packages\requests\cookies.py", line 329, in __getitem__
return self._find_no_duplicates(name)
File "C:\Users\Me\Envs\proj\lib\site-packages\requests\cookies.py", line 400, in _find_no_duplicates
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
KeyError: "name='csrftoken', domain=None, path=None"

The problem is that your first request logs the user in. Django rotates the token when you log in:
Why might a user encounter a CSRF validation failure after logging in?
For security reasons, CSRF tokens are rotated each time a user logs in. Any page with a form generated before a login will have an old, invalid CSRF token and need to be reloaded. This might happen if a user uses the back button after a login or if they log in in a different browser tab.
The token that you use in the next requests is the token that was used before it was rotated. You need to get the new token from the cookie after the login request.
On top of that, requests transparently follows redirects, and return the last response. Since the second response (probably) doesn't use the token, it isn't set as a cookie. You can use allow_redirects=False to get the first request, and then get the new token from that request. Alternatively you can sent a new GET request for a page that uses the token in the response body, then the token will also be sent as a cookie.
...
first_response = session.post(CURRENT_URL.format('api-auth/login/'),
data={'username': 'itsme', 'password': 'password'},
headers={'X-CSRFToken': csrftoken},
allow_redirects=False)
# Get the new token
newcsrftoken = first_response.cookies['csrftoken']
response = session.post(CURRENT_URL.format('api-v1/languages/'),
params={'name': "French", "audio_base": "adpifajsdpfijsdp"},
headers={'X-CSRFToken': newcsrftoken})

You're likely not using sessions with Requests which would keep the initial response cookies for the next requests.
DRF tells you more about how CSRF works with DRF at http://www.django-rest-framework.org/topics/ajax-csrf-cors/#csrf-protection

Related

How to use Postman to authenticate Google Login with dj_rest_auth

So I am following the official documentation for Google sign in with DjangoRestFramework using DJ Rest Auth (this link)
I intend to authenticate with Postman Oauth2 (by following the guide and generating an Access Token)
Postman is generating an access token successfully, but I cannot seem to use this authentication in my API calls. Please who knows which step I am missing - I want to handle everything in Postman.
urls.py
urlpatterns = [
path('', Home.as_view(), name='home'),
path('admin/', admin.site.urls),
path('accounts/', include(api_urls, namespace='api')),
path('accounts/login/', GoogleLogin.as_view(), name='google_login'),
path('accounts/', include('rest_framework.urls')),
]
views.py
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = 'http://localhost:8080/accounts/google/login/callback/'
client_class = OAuth2Client
On calling an API endpoint, I get an invalid token error:
If I however visit the Google Login view in my RestFramework UI (in my case http://localhost:8080/accounts/login), I get an endpoint to make a POST, and on making a POST request, a key is generated. Only this key (if used as a Bearer token) works in my API calls.
How can I authenticate on Google, and make my API calls independent of the DRF UI?
Callback URL has been configured on my Google Developer Client.
PS: I feel the answer is in step 6 of the documentation, but I am unable to figure out how to do this in Postman
POST code or token to specified URL(/dj-rest-auth/google/)
What I did here is from postman go to headers then put Authorization = Token youraccesskey
which in your case Authorization = Token ef057......
Hope it helps

Requests to localhost from within a view cause the server to stop responding

I am using Djoser to create an authentication backend. I want the following to happen:
User registers at http://localhost:8000/auth/users/ with email, username, password, and re_password
User receives an activation email at the email they entered with a URL to activate their account. The URL is of the form: <web url>/activate/{uid}/token (eg: http://localhost:8000/activate/Mg/apur8c-6100390a061c5ff081235193867f943a)
Clicking on that URL sends a GET request to the server where a view (UserActivationView) receives the extracted uid and token and sends a request of its own to <web url>/users/activation/{uid}/token (http://localhost:8000/users/activation/Mg/apur8c-6100390a061c5ff081235193867f943a). And returns the response that it gets from that request as its response.
User account gets activated.
This is the code that does all of the above:
# Djoser configuration in settings.py
DJOSER = {
'LOGIN_FIELD': 'email',
'PASSWORD_RESET_CONFIRM_URL': 'password-reset/{uid}/{token}',
'USERNAME_RESET_CONFIRM_URL': 'username-reset/{uid}/{token}',
'SEND_ACTIVATION_EMAIL': True,
'ACTIVATION_URL': 'activate/{uid}/{token}',
'SEND_CONFIRMATION_EMAIL': True,
'PASSWORD_CHANGE_EMAIL_CONFIRMATION': True,
'USERNAME_CHANGED_EMAIL_CONFIRMATION': True,
'USER_CREATE_PASSWORD_RETYPE': True,
'SET_PASSWORD_RETYPE': True,
}
# views.py in user app (users/views.py)
from rest_framework.views import APIView
from rest_framework.response import Response
import requests
class UserActivationView(APIView):
def get(self, request, uid, token):
protocol = "https://" if request.is_secure() else "http://"
web_url = protocol + request.get_host()
post_url = web_url + '/auth/users/activation/'
post_data = {'uid': uid, "token": token}
print(f"Sending request to {post_url} with {post_data}")
result = requests.post(post_url, data=post_data) # <<-- causes the server to hang
return Response(result.text)
# users/urls.py
from django.urls import path, include
from .views import UserActivationView
app_name = "users"
urlpatterns = [
path('auth/', include('djoser.urls')),
path('auth/', include('djoser.urls.jwt')),
path('activate/<str:uid>/<str:token>/',
UserActivationView.as_view())
]
# MAIN project urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('chat/', include('frontend.urls')),
path("chat/", include('backend.urls')),
path('api/accounts/', include('backend.urls')),
path('', include('users.urls'))
]
Now, the problem arises in step 3 (at the result = requests.post(post_url, data=post_data) line). When the view attempts to send a post request the server hangs for quite some time. During this, it doesn't even halt when I do CTRL + C. This is the output that I get (I have added comments):
HTTP POST /auth/users/ 201 [5.87, 127.0.0.1:56239] ## User registration was successful and email was sent
HTTP GET /activate/Mg/apuvq9-3461158f807a1e054738458bc2372486 301 [0.00, 127.0.0.1:56245] # 301 response while trying to GET the URL sent in the email
Sending request to http://localhost:8000/auth/users/activation/ with {'uid': 'Mg', 'token': 'apuvq9-3461158f807a1e054738458bc2372486'} # My print statement
# A long pause here. Server hangs and then this error appears:
Application instance <Task pending coro=<StaticFilesWrapper.__call__() running at D:\pigeon\venv\lib\site-packages\channels\staticfiles.py:44> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at C:\ProgramData\Anaconda3\lib\asyncio\futures.py:348, <TaskWakeupMethWrapper object at 0x000001E3EDE96858>()]>> for connection <WebRequest at 0x1e3edd8a788 method=GET uri=/activate/Mg/apuvq9-3461158f807a1e054738458bc2372486/ clientproto=HTTP/1.1> took too long to shut down and was killed.
Application instance <Task pending coro=<StaticFilesWrapper.__call__() running at D:\pigeon\venv\lib\site-packages\channels\staticfiles.py:44> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at C:\ProgramData\Anaconda3\lib\asyncio\futures.py:348, <TaskWakeupMethWrapper object at 0x000001E3EE1067C8>()]>> for connection <WebRequest at 0x1e3edd8a788 method=GET uri=/activate/Mg/apuvq9-3461158f807a1e054738458bc2372486/ clientproto=HTTP/1.1> took too long to shut down and was killed.
Internal Server Error: /activate/Mg/apuvq9-3461158f807a1e054738458bc2372486/
Traceback (most recent call last):
File "D:\pigeon\venv\lib\site-packages\asgiref\sync.py", line 482, in thread_handler
raise exc_info[1]
File "D:\pigeon\venv\lib\site-packages\django\core\handlers\exception.py", line 38, in inner
response = await get_response(request)
File "D:\pigeon\venv\lib\site-packages\django\core\handlers\base.py", line 238, in _get_response_async
)(e, request)
File "D:\pigeon\venv\lib\site-packages\asgiref\sync.py", line 444, in __call__
ret = await asyncio.wait_for(future, timeout=None)
File "C:\ProgramData\Anaconda3\lib\asyncio\tasks.py", line 414, in wait_for
return await fut
concurrent.futures._base.CancelledError
Alternatively, if I manually send an activation request via Postman or a separate Python script then the request works just fine and the user is activated:
The problem only seems to arise when the request is made from within the view to the server. I also tried result = requests.get('http://localhost:8000/chat') as an experiment and it too caused the server to hang whereas result = requests.get('http://google.com') causes no such issues.
The view's code was taken from here and someone under the answer also commented:
This hangs my debug django server – Hack_Hut Apr 25 at 11:55
How can I resolve this?
I am using:
Python: 3.7.5
Django: 3.2.5
Djoser: 2.1.0
Django rest framework: 3.12.4
Requests: 2.26.0

How to use DRF JWT resfresh

I can generate token,However, after the Web accesses me with the first token, I cannot give a new token
I set it in setting
'JWT_ALLOW_REFRESH': True,
But I don't know how to get a new one
Please let me know if you need anything else
I thought that after this setting is completed, the token will be changed automatically Medium expiration time,Looks like I'm wrong
based on this post, you have to do the following:
request the token http post http://127.0.0.1:8000/api/token/ username=vitor password=123
this returns a access token and a refresh token
use the access token to access the site
if the access token expires (site returns 403) use the refresh token to get a new valid access token http post http://127.0.0.1:8000/api/token/refresh/ refresh=REFRESHTOKEN
Note that the refresh token can also expire, then you would have to restart the flow.
EDIT: code snippets
install library
pip install djangorestframework_simplejwt
docs of the library
settings.py
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
urls.py
from django.urls import path
from rest_framework_simplejwt import views as jwt_views
urlpatterns = [
# Your URLs...
path('api/token/', jwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
]

unable to make a successful call from android using retrofit with csrf token

I'm new to django and got struck with csrf tokens. I'm making a post request from android using retrofit to my django server which is using csrf protection. I had obtained the csrf token by making a get request first and then I'm passing this csrftoken from the body of POST request. However, my server is showing 'CSRF cookie not set' error. The server is responding well to the calls from POSTMAN but when I make calls from android, I get this error. I think there is some simple thing I'm missing, but I'm not able to figure it out.
Session based authorization is usually used in web-apps. In case of android apps which are backed by API.
So rather than you can do Token Based Authorization using rest_framework in Django.
In your settings.py
INSTALLED_APPS = [
...
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication', # <-- And here
],
}
Now migrate the migrations to the database.
python manage.py migrate
Run this command to generate token for the specific user.
python manage.py drf_create_token <username>
Now add this line to urls.py.
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
#Some other urls.
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
]
Using this you can obtain token for any user by using its username & password by just passing them in request body.
So this will be our protected api. Add this class based view in your views.py
from rest_framework.permissions import IsAuthenticated,AllowAny # <-- Here
from rest_framework.views import APIView
from rest_framework.response import Response
class DemoData(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request):
content = {'data': 'Hello, World!'}
return Response(content)
Now pass a header with the api name as 'Authorization' & value be like something 'Token 5a2b846d267f68be68185944935d1367c885f360'
This is how we implement Token Authentication/Authorization in Django.
For more info, click here to see official documentation.

Django's REST framework custom exception handling doesn't seem to work as advertised

July 2019 - Using latest Django/DRF:
in myproj/my_api_app/public_views.py I have:
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
in myproj/myproj/settings.py I have: (Yes, two times myproj/myproj)
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'public_get': '30/minute',
'user_get': '60/minute'
},
'EXCEPTION_HANDLER': 'myproj.my_api_app.public_views.custom_exception_handler'
}
I try to hit a non-existing endpoint on purpose and I am still getting the default 404 message. I have DEBUG=False in settings.
I have tried every permutation of the "Path" for the EXCEPTION_HANDLER. Nothing works. The EXCEPTION_HANDLER is simply ignored. custom_exception_handler is never called.
What am I doing wrong?
The rest-framework exception view doesn't work for invalid route. Try to send an invalid request to an API route to see it in action.