How can I use Flask WTForms and CSRF without session cookie? - python

I have a very simple app that has no user management or any Flask-Login auth needs. It has forms, WTForms. All I want to do is collect some data submitted by the form. I could technically disable CSRF validation but Flask WTForms really urges me not to.
I'd like to disable flask session cookie in the browser because it seems unnecessary and I would need to put a cookie banner for GDPR compliance. So to avoid all that, I thought of disabling flask session cookie as follows:
class CustomSessionInterface(SecureCookieSessionInterface):
""" Disable session cookies """
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool:
return False
# App initialization
app = Flask(__name__)
app.session_interface = CustomSessionInterface()
But doing so leads to a 500 error: "The CSRF session token is missing". However, looking at the HTML that was rendered has the following csrf token rendered properly:
<input id="csrf_token" name="csrf_token" type="hidden" value="ImI2ZDIwMDUxMDNmOGM3ZDFlMTI4ZTIzODE4ODBmNDUwNWU3ZmMzM2Ui.YhA2kQ.UnIHwlR1qLL61N9_30lDKngxLlM">
Questions:
What is the relationship between CSRF token validation and session cookie? Why is a cookie necessary to validated the CSRF token?
I tried enabling session cookies again, deleting the cookie in Chrome developer tools leads to the same error. So, indeed, session cookie seems to be absolutely necessary to validate CSRF token.
How can I use CSRF form validation without a session cookie?
Thank you so much.

I found out from the code base of WTForms: https://github.com/wtforms/flask-wtf/blob/565a63d9b33bf6eb141839f03f0032c03894d866/src/flask_wtf/csrf.py#L56
Basically, session['csrf_token'] is stored in the session and compared against the form.hidden() tag (or form.csrf_token) in the HTML body.
This is not clearly explained in the docs. But the codebase makes it clear. I guess there is no way to do CSRF protection without secure cookies.
The downside of this is that you can't get rid of cookies. I suspect, one could build a server-side session database, but then there are issues with scaling your Flask app horizontally.

You should check if it helps you:
https://wtforms.readthedocs.io/en/3.0.x/csrf/#creating-your-own-csrf-implementation
This allows you to create your own implementation of csrf token generation and validation in which you do not need to use cookies.

Related

How to send POST request to Flask API employing CSRF token from mobile device (android) app?

Question:
How can I POST data from my android app to my flask web app which is employing CSRF protection?
Background:
I've built a website using Flask, and have protected it from CSRF attacks by globally deploying CSRFProtect(), which comes from the Flask-WTForms package.
I am building a phone app that will allow a user to automatically send data to their account on the Flask database every day.
I can successfully access the Flask API using a GET request from my android app.
I am unable to successfully send a POST request from my android app, unless I turn off global CSRF protection within my Flask API.
My thoughts so far:
Option one - turn off CSRF protection if request is coming from an application.
From reading I understand that CSRF attacks require cookies, which are only generated by browsers, and thus if my request is coming from my app, then I am safe from CSRF attacks and could turn off CSRF protection for a specific URL. BUT, this URL could be accessed by anyone if they were to discover it, so I would need to keep CSRF protection on if the request was coming from a browser, and turn it off if coming from my android app. Is this possible?
Option two - get the CSRF token on my android app.
I don't think that coding the token into my app would be safe, as anyone would be able to download the app and potentially access the code (right?). If that's true, then I would need to somehow get the token from Flask via an authentication process with the Flask app. BUT, how can I send form data to the flask app if CSRF protection is blocking my POST requests?
Please advise. Normally with enough googling I can figure out an answer, but on this I'm stuck!
Thank you!
You have not provided enough information here, but I faced a similar issue when I started learning about flask. So, I think this should be a similar case for you too.
I was creating a simple webhook that would accept POST requests from another application. If I turned CSRF off, POST requests would work, but with CSRF protection turned on, POST requests returned with a 400 status code.
There is a simple way to exempt any views or blueprints in Flask from CSRF protection. We can decorate the route that does not need the csrf protection with a flask_wtf.csrf.CSRFProtect.exempt decorator. Please look at the
below code.
from flask import Flask, request, make_response, jsonify
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__) # this will take name of the project
csrf = CSRFProtect()
csrf.init_app(app)
#app.route#app.route("/newhook", methods=['GET', 'POST'])
#csrf.exempt #this will exempt the csrf for this view
def newhook():
if request.method == 'POST':
alldata = request.get_json()
resp = alldata['message']
num = alldata["from"]
myres = make_response(jsonify(resp, num))
return myres
I am also providing a link to the official flask_wtf CSRF protect documentation below for reference.
https://flask-wtf.readthedocs.io/en/0.15.x/csrf/#exclude-views-from-protection
Hope this helps!!

django: rest-auth and allauth for Facebook registration API requires CSRF token

Using allauth and django-rest-auth to create a facebook login api. I've followed the both packages documentations and using example from rest-auth doc. I've followed all the steps and I can successfully use this API from DRF browse-able API view and it is successfully performing the registration. When I try this API from somewhere else like postman it asks for CSRF token.
I tried to use csrf_exempt decorator but that doesn't seem to be effective on this url.
Here is my url config:
url(r'^rest-auth/facebook/$', csrf_exempt(FacebookLogin.as_view()), name='fb_login'),
Rest of the things are same as they mentioned in the documentation for django-rest-auth.
I can't figure out what am I missing, or where should I look for a fix. Any help to diagnose the issue would be appreciated.
TL;DR Set authentication_classes = () in your FacebookLogin view.
The login and register views are supposed to have no authentication checks. I assume you have your DRF DEFAULT_AUTHENTICATION_CLASSES set to token auth and session auth in the order. So when your request is processing the server doesn't find auth token so as a fallback it tries to do session auth which requires CSRF which inturn causes the CSRF failure.

User authentication for Flask API on mobile

I am making an API in Flask for a simple jQuery mobile + PhoneGap app to be packaged and downloaded. As I understand, all the calls to the database should be made with JavaScript, AJAX, and JSON. Since the app is about the user, and all of the views draw data from the logged user, I am not sure how to proceed with the authentication. As I understand, the workflow should be:
user logs in (json encoded username and password)
server generates token with expiration (i.e. 24h) for that user
this token is saved on the mobile app as a cookie or in localstorage
all of the calls to the server are done with this token which identifies the current user: /api/token=12345
when the toke expires, a new login prompt is required
I thought of implementing this with Flask-Security's authentication token. Is there a more straightforward way of accomplishing this?
Flask-JWT seems like a pretty straight-forward solution.
Then on the front end you can just add an HTTP interceptor to add X-Auth-Token to the headers or something.

Does SessionAuthentication work in Tastypie for HTTP POST?

I am able to do GET to work with SessionAuthentication and Tastypie without setting any headers except for content-type to application/json. HTTP POST however just fails even though the Cookie in the Header has the session id. It fails with a 401 AuthorizationHeader but it has nothing to do with Authorization. Changing SessionAuthentication to BasicAuthentication and passing username/password works too.
Has anyone ever got SessionAuthentication to work with POST with Tastypie?
Yes I have gotten it to work. All you need to do is to pass the csfr token:
SessionAuthentication
This authentication scheme uses the built-in
Django sessions to check if a user is logged. This is typically useful
when used by Javascript on the same site as the API is hosted on.
It requires that the user has logged in & has an active session. They
also must have a valid CSRF token.
This is how you do that in jQuery:
// sending a csrftoken with every ajax request
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
});
$.ajax({
type: "POST",
// ...
Notice the part that says $.cookie('csrftoken'). It gets the csrf token from a cookie that Django sets.
Update:
I had some problems with Django not setting the cookie on Firefox and Opera. Putting the template tag {% csrf_token %} in your template solves this. The right solution would probably be to use the decorator ensure_csrf_cookie().
Here are some additions to Dan's answer. Please, correct me if something is wrong, I am still a bit confused about it myself.
Before we continue, read about CSRF protection in Django. Read it carefully. You need to put the token from the cookie into the header X-CSRFToken. This will not work if the cookie is Httponly, that is, if you have set CSRF-COOKIE-HTTPONLY = True in settings.py. Then you have to embed the cookie in the document which, of course, creates further vulnerabilities and reduces protection the gained by using Httponly.
As far as I can tell, if the cookie is not Httponly, jQuery sets X-CSRFToken automatically. Correct me if I am wrong, but I've just spent several hours playing with it, and this is what I am consistently getting. This makes me wonder, what is the point of the advice in Django documentation? Is it a new feature in jQuery?
Further discussion:
Tastypie disables CSRF protection except with Session Authentication, where it has custom code in authentication.py. You have to pass both the cookie csrftoken cookie and the header X-CSRFToken for the authentication to work. (This is Tastypie's requirement.) Assuming same domain, the browser will pass the cookies. JQuery will pass the header for you unless the csrftoken cookie is Httponly. Conversely, if the cookie is Httponly, I was unable to even manually set the header in $.ajaxSetup{beforeSend.... It appears that jQuery automatically sets X-CSRFToken to null if the csrftoken cookie is Httponly. At least I was able to set the header X-CS_RFToken to what I wanted, so I know I passed the value correctly. I am using jQuery 1.10.
If you are using curl for testing, you have to pass two cookies (sessionid and csrftoken), set the headers X-CSRFToken and, if the protocol is HTTPS, also set the Referrer.
I found this in the tastypie source code. Basically implies that HTTP POST is not supported by SessionAuthentication.
class SessionAuthentication(Authentication):
"""
An authentication mechanism that piggy-backs on Django sessions.
This is useful when the API is talking to Javascript on the same site.
Relies on the user being logged in through the standard Django login
setup.
Requires a valid CSRF token.
"""
def is_authenticated(self, request, **kwargs):
"""
Checks to make sure the user is logged in & has a Django session.
"""
# Cargo-culted from Django 1.3/1.4's ``django/middleware/csrf.py``.
# We can't just use what's there, since the return values will be
# wrong.
# We also can't risk accessing ``request.POST``, which will break with
# the serialized bodies.
if request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
return request.user.is_authenticated()
So answering my own question here but if someone can explain it better and suggest a good way to do this, that would be great too.
Edit
I am currently using a workaround from https://github.com/amezcua/TastyPie-DjangoCookie-Auth/blob/master/DjangoCookieAuth.py which basically is a custom authentication scheme that fetches the session_id from the cookie and checks with the backend if it is authenticated. Might not be the most full proof solution but works great.

How can I create a session-local cookie-aware HTTP client in Django?

I'm using a web service backend to provide authentication to Django, and the get_user method must retain a cookie provided by the web service in order to associate with a session. Right now, I make my remote calls just by calling urllib2.urlopen(myTargetService) but this doesn't pass the cookie for the current session along.
I have created a session access middleware to store the session in the settings:
class SessionAccessMiddleware:
def process_request(self, request):
settings.current_session = request.session
So, I can access the request session in get_request and post_request, but I don't know how to have urllib2 remember my cookies in a session-specific way.
How do I do this?
Here: http://docs.python.org/library/cookielib.html#examples are examples of doing exactly what you try to do with urllib2 and cookielib. So according to docs you need to create cookielib.CookieJar, set cookie with correct data (from session), build an opener that uses your CookieJar and use it to fetch yourTargetService.
If settings in your middleware code means from django.conf import settings it's not good idea. Look at http://github.com/svetlyak40wt/django-globals/ for a place where you can safely store request-wide data for access from somewhere where request object is unaccessible. Also, it would be probably good idea to write custom authentication backend and use it with django.contrib.auth - instead of rolling your own auth system from scratch - which is covered here: http://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend .

Categories