I read in the docs that DRF only validates CSRF tokens on authenticated requests and login views should explicitely check the CSRF token.
Problem is, how does one manually check the CSRF token?
In my settings.py:
MIDDLEWARE = [
...
"django.middleware.csrf.CsrfViewMiddleware",
...
]
Here's my view:
from rest_framework.decorators import api_view
from django.http import JsonResponse
from django.views.decorators.csrf import get_token
# I have to manually generate the csrf token and put it in the response body, because I use react-native and that can't read the token from the 'Set-Cookie' header
#api_view(["GET"])
def user_info(request):
return JsonResponse({"csrf_token": get_token(request)})
#api_view(["POST"])
def login(request):
return JsonResponse({"foo": "bar"})
When I make a POST request in the frontend and I don't provide the CSRF token, it should fail, but I actually receive the {"foo": "bar"} JSON.
I tried the #csrf_protect and #requires_csrf_token decorators, but the request is still not failing.
I also tried this, but that errors on required positional argument: 'get_response
CsrfViewMiddleware().process_view(request, None, (), {})
If I pass a function to get_response, that function is never called:
def test_get_response(req):
breakpoint()
CsrfViewMiddleware(get_response=test_get_response).process_request(request)
I checked that the CSRF token is not passed in the headers, not the cookies, and I tried different browsers and incognito mode.
How do I get my api requests to fail if they don't include a valid CSRF token?
You're having error in this importing from django.views.decorators.csrf import get_token . you should import get_token from django.middleware.csrf .
according to django documentation and it return the CSRF token required for a POST form.
Okay... a few rabbit holes later, I found some kind of solution.
Django uses Double Submit Cookie for CSRF validation. This means:
Frontend requests CSRF token from Django
Django generates token, sends it to FE in Set-Cookie header
FE puts csrf in cookie, which cannot be altered by a potential attacker
FE puts the same token in POST form and sends it to Django
Django checks if token in POST form and cookie are the same
Problem is, React Native doesn't support cookies, so it has to send the CSRF token some other way. Problem with that is there is not way to do that securely.
I solved it by writing custom middleware for django:
FE requests CSRF token from Django
Django generates token, sends it to FE, AND stores hashed version in db
FE puts it in POST form and sends it to Django
Django hashes the token, checks if hash is in db
Upon successful validation, Django removes token from db
A CRON job removes any unused tokens after 24 hours
Bit cumbersome, but I only have to validate this way on web requests.
Related
I am making part of the web app where (unlogged users, all visitors) can fill the form and I have to save that data (name, phone number, question) in database..
I am making REST using Django, but for frontend I will use React or Django, and I am making POST method in Django framework that reads JSON data from POST https request, and then I save that data to database.. But I get error CSRF verification failed. Request aborted. because I don not use CSRF token, because it is not required for this (I do not have logged users).
But I do not want to disable CSRF, because for admin page I would use this to authenticate users?
So anyone knows how to avoid using CSRF token for this method? Thanks in advance..
def saveDataToDatabase(request):
if request.method == 'POST':
json_data = json.loads(request.body)
try:
data = json_data['data']
except KeyError:
HttpResponseServerError("Malformed data!")
This is the part of method..
It's possible to disable csrf protection on a view with #csrf_exempt decorator.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def saveDataToDatabase(request):
# Your code here
More Infos on the Django doc
Have you thought about using Views to handle the requests? You may use these so you don´t have to do the next advice I´m giving you.
If the first advice wasn´t good enough, you may disable the csrf token in the settings file from Django. You need to remove (or comment) the django.middleware.csrf.CsrfViewMiddleware in the available Middleware list.
Any other question just ask. ;)
I have a backend API, it's in django and deployed on Google Endpoint.
I have a post request that insert data to my DB.
I created a script to use this endpoint but I got this error:
{"detail":"CSRF Failed: Referer checking failed - no Referer."}
Regarding over posts I added the crsf_exempt decorator to my class but it did not change.
I try to add the decorator two ways:
class AddUser(APIView):
""" Create user and company from csv """
#method_decorator(csrf_exempt)
def post(self, request):
#method_decorator(csrf_exempt, name='dispatch')
class AddUser(APIView):
""" Create user and company from csv """
def post(self, request):
But both failed.
This is how I contact my endpoint:
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
Any ideas ?
Thanks
EDIT
So I tried to add authentication classes to my views but it appears to be a bad idea. This is being real trouble for me.
I tried to get the csrftoken doing like this:
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
print(client.cookies)
if 'csrftoken' in client.cookies:
# Django 1.6 and up
csrftoken = client.cookies['csrftoken']
else:
# older versions
csrftoken = client.cookies
Thing is, I am using IAP to protect my API and I do not have any csrftoken cookie but I do have a something looking like this:
<RequestsCookieJar[<Cookie GCP_IAP_XSRF_NONCE_Q0sNuY-M83380ypJogZscg=1
for ...
How can I use this to make post request to my API ?
So this happened to me because I did not set any authentication_classes to my generic view.
When this option is not set Django automatically use the SessionBackend, which need the csrf token.
I fixed it by adding this to my view: authentication_classes = [ModelBackend, GoogleOAuth2]
#Kimor - Can you try doing this in your urls.py
from django.views.decorators.csrf import csrf_exempt
url('^test/$', csrf_exempt(views.TestView.as_view())),
The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the Django router expects is not actually instantiated until you call TestView.as_view().
source
Django REST Framework CSRF Failed: CSRF cookie not set
So after working on this project for a while this is what I learned regarding the CSRF using Django.
First of all, if you are using django templates, or in any cases where your back-end and front-end are running behind the same server the most common practice is to use session for authentication.
This is activated by default by DRF.
This means that in your DRF configuration if you do not explicitly set the DEFAULT_AUTHENTICATION_CLASSES option default authentication will be set to Session + BasicAuth.
In this configuration you'll need to manage the CSRF token as described in the documentation (https://docs.djangoproject.com/en/4.0/ref/csrf/).
If your back-end and front-end are separated as in my case, using CSRF is not the only solution or even the recommended one.
As in my case I use JWT behind IAP (Identity Aware Proxy, provided by google). I had to write my own authentication classes and then use it in my DRF conf:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'main.authentication_backend.custom_auth.IAPAuthentication'],
...
}
Here is explain how to write your own authentication class: https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication.
I am trying to send a JSON response from Django back-end to my angular front-end.
When I make the request I receive nothing in Postman or Angular but,opening the link in browser seems to be returning the correct result
My View is :
#api_view(['GET'])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When I open http://127.0.0.1:8000/accounts/get_username/ in browser I receive
{"username": "aditya8010"} on the web page.
But when i do a get request using POSTMAN I recieve
{
"username": ""
}
Same with Angular
this.http.get("http://127.0.0.1:8000/accounts/get_username/").subscribe((res) => {
this.username = JSON.stringify(res["username"])
console.log(this.username," ", res)
})
this code also prints an empty username string.
Another thing I have noticed is that my print statement in the view does print anything random I put in there when called from POSTMAN or Browser but when I use request.user.username it doesnt print anything when called by POSTMAN.
And each time the response code is 200
What am I doing wrong.
When you're sending the request you are not providing authentication credentials (i.e. something that identifies the user that is sending the request). How do you obtain this credentials?
You need to establish an authentication method. There are several but I recommend using Token authentication with knox package. Basically, you have an endpoint that logins the user with his username and password (normal json post request) and that endpoint returns a token. This token is what identifies the user. You send this token in the header of each request you need to be authenticated. That means you probably should include an IsAuthenticated permission for the view. In postman:
API view:
from rest_framework.permissions import IsAuthenticated
#api_view(['GET'])
#authentication_classes([IsAuthenticated])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When it is in a browser, your login information is remembered in the session. When using postman or Angular, you need to provide the user's information in the request header manually.
I'm not sure I'm right on track. Please give me a hint or direction.
I set up my Web service using Django and also made mobile app with React Native using Django REST framwork. Django uses the basic session authentication, but Django REST API uses token authentication to process the request from mobile app.
I want to implement small ReactJS app into my existing Django web. At this stage, I think my small react app will need auth token to communicate with REST api for itself.
So, my idea is that when user logs in web login page, user's API token needs to be received from API and save into cookie or localStorage while normal log in process is processing in Django Web service. Because I don't want to let users log in again to run react app on my web page to get auth token.
Am I right on track? if so, how can I make it works? Please refer to my code in Django login view.py Do i need to some code in order to get API auth token and save it into client side?
def Login(request):
if not request.user.is_authenticated:
if request.method == "POST":
email = request.POST['email']
password = request.POST['password']
user = authenticate(email = email, password = password)
if user is not None:
login(request, user)
messages.add_message(request, messages.SUCCESS, request.user.nickname + ' Welcome!')
return redirect('Search')
else:
messages.add_message(request, messages.WARNING, 'Please check Email / Password again')
return redirect('login')
else:
form = LoginForm()
return render(request, 'login.html', {'form': form })
else:
return redirect('main')
You have done some useless in your login function. you can use jwt. it has some good function for supporting login. In its login function, when send username and password with post, it return token to client.
http://getblimp.github.io/django-rest-framework-jwt/
You just need set urlpattern
from rest_framework_jwt.views import obtain_jwt_token
#...
urlpatterns = [
'',
# ...
url(r'^api-token-auth/', obtain_jwt_token),
]
It return token
$ curl -X POST -d "username=admin&password=password123" http://localhost:8000/api-token-auth/
In other request, if you need authentication, use following request
$ curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/
They both carrying out similar tasks with few differences.
Token
DRF's builtin Token Authentication
One Token for all sessions
No time stamp on the token
DRF JWT Token Authentication
One Token per session
Expiry timestamp on each token
Database access
DRF's builtin Token Authentication
Database access to fetch the user associated with the token
Verify user's status
Authenticate the user
DRF JWT Token Authentication
Decode token (get payload)
Verify token timestamp (expiry)
Database access to fetch user associated with the id in the payload
Verify user's status
Authenticate the user
Pros
DRF's builtin Token Authentication
Allows forced-logout by replacing the token in the database (ex: password change)
DRF JWT Token Authentication
Token with an expiration time
No database hit unless the token is valid
Cons
DRF's builtin Token Authentication
Database hit on all requests
Single token for all sessions
DRF JWT Token Authentication
Unable to recall the token without tracking it in the database
Once the token is issued, anyone with the token can make requests
Specs are open to interpretations, no consensus on how to do refresh
Reference: Django : DRF Token based Authentication VS JSON Web Token
I'd like to leave my answer after I solved in my way through my long research and study. My solution is quite simple.1. set DRF session authentication enable. Adding some code in setting.py
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
2. add 'credentials: "include"' into fetch code to use already logged in session cookie for authentication.
await fetch(API_URL, {
credentials: "include"
})
this solution solved my case.
This question already has answers here:
How do you authenticate a websocket with token authentication on django channels?
(8 answers)
Closed 3 years ago.
I am using a frontend framework (Vuejs) and django-rest-framework for the REST API in my project. Also, for JSON web token authentication I am using django-rest-framework-jwt. After a successful login, the user is provided with a token. This token is passed into every request to fetch any API related stuff.
Now I would like to integrate django channels into my project. So, after successful login, when the token is received in the client side, I would like to initiate a websocket connection. Then on the server (consumer), I would like to check if the requested user is not anonymous. If the requested user is anonymous, I would like to close the connenction or else accept it.
This is how I have till now:
client side:
const socket = new WebSocket("ws://" + "dev.site.com"+ "/chat/");
routing.py:
channel_routing = [
route("websocket.connect", ws_connect),
...
...
]
consumers:
def ws_connect(message):
# if the user is no anonymous
message.reply_channel.send({
"accept": True
})
# else
message.reply_channel.send({
"close": True
})
In the documentation there's a decorator #channel_session_user_from_http which will provide a message.user. But I am using a token instead of a session. How can I check a user on connection when using token authentication, so that I can accept or close connection. Or, if there is a better way could you please advise me with it. Thank you.
The problem is that the browsers do not support passing jwt auth headers on websocket upgrade, so that's basically it. I faced this problem some time ago and came up with the solution of passing the token via query parameters - note that this is totally insecure without TLS as you expose the authentication in the URI. I don't have the access to the exact code anymore, but here is the idea:
from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError
class Consumer(JsonWebsocketConsumer):
def connect(self, message, **kwargs):
# construct a fake http-like request object from the message
message.content.setdefault('method', 'FAKE')
request = AsgiRequest(message)
# validate the token
try:
VerifyJSONWebTokenSerializer().validate(request.GET)
super().connect(message, **kwargs)
except (KeyError, InvalidTokenError, ValidationError,):
# token is either not available or invalid
# so we disconnect the user
message.reply_channel.send({'close': True})
Register the consumer with
channel_routing = [
...
route_class(Consumer, path=r'^my-ws-endpoint$'),
]
On browser side, you can establish the websocket connection by passing the token as query parameter in the websocket URI:
let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);
You can then extract the auth check code in a decorator similar to #channel_session_user_from_http and just decorate your connection routines, or extract the code to a mixin if you use class-based routes.
I would like to repeat though that this approach is totally insecure without using encryption, so in production you URIs should start with https/wss.
Edit: here is a pretty nice solution for DRF token auth, suitable for both function-based and class-based routes. It has pretty much the same approach as mine, constructing a request object and passing it to the authenticator.
#hoefling's answer was my guide. I was confused about two things on authenticating a user.
What to do with the token?
You can pass the token as a query string and get that query params. Read more about how to get the query params here.
Or if you are already passing it in the request's authorization header, you can get it from there like #hoefling did with his answer. Remeber to first fake that request.
How validate that token and get the user?
Finally VerifyJSONWebTokenSerializer class was all I needed to validate the token, and get that token's user object. (Thanks #hoefling!) You can read the actual code of django-rest-framework-jwt here.
So, I ended up doing this way:
def ws_connect(message):
message.content.setdefault('method', 'FAKE')
django_request = AsgiRequest(message)
token = django_request.GET['token'].split(' ')[1]
try:
data = {'token': token}
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
...
...
message.reply_channel.send({
"accept": True
})
except (KeyError, InvalidTokenError, ValidationError,):
...
...
message.reply_channel.send({
"text": "Authentication error",
"close": True
})