How to send CSRF token using Django API and a Flutter-web frontend ? HeaderDisallowedByPreflightResponse [duplicate]
I have a python/django web API with a single endpoint, let's call it /api/v1/form
. That API is called from a Flutter-web frontend application. I currently use the following configuration that disables CSRF token verification, and it works :
requirements.txt
Django==5.1.7
django-cors-headers==4.7.0
webserver/settings.py
...
ALLOWED_HOSTS = ["localhost"]
CORS_ALLOWED_ORIGINS = ["http://localhost:8001"] # flutter dev port
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'webserver',
]
...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
webserver/urls.py
from django.urls import path
import webserver.views
urlpatterns = [
path('api/v1/form', webserver.views.api_v1_form, name="api_v1_form"),
]
...
webserver/views.py
from django.http import HttpResponse, HttpResponseBadRequest
def api_v1_form(request):
if request.method == "POST":
process_request(request.body)
return HttpResponse("request successfully processed.")
return HttpResponseBadRequest("Expected a POST request")
flutter/lib/page_form.dart
Future<int> sendForm(MyData data) async {
final response = await http.post(
Uri.parse("http://localhost:8000/api/v1/form"),
body: data.toString(),
);
return response.statusCode;
}
Here is what I don't understand : if I disable to the CORS package in order to simply use a vanilla Django server, then I find myself capable of sending requests to the API but unable to receive an answer. Why is that the case ?
The following is the configuration used to get the CSRF token and use it in the requests.
settings.py
ALLOWED_HOSTS = ["localhost"]
CSRF_TRUSTED_ORIGINS = ["http://localhost:8001", "http://localhost:8000"]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'webserver',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
With only the settings.py changed, I get a 403 forbidden
answer.
views.py
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def api_v1_form(request):
... # unchanged
The answer is still 403 forbidden
Lastly, I tried to add a /api/v1/token
API to send the token and reuse it in the request.
views.py
def api_v1_token(request):
if request.method == "GET":
return HttpResponse(get_token(request))
return HttpResponseBadRequest()
@ensure_csrf_cookie
def api_v1_form(request):
if request.method == "OPTIONS": # POST -> OPTIONS
# ...
page_form.dart
Future<int> envoyerFormulaire(MyData data) async {
final tokenResponse = await http.get(
Uri.parse("$apiUrlBase/token"),
);
final token = tokenResponse.body.toString();
Uri uriPost = Uri.parse("$apiUrlBase/form");
final dataResponse = await http.post(
uriPost,
body: data.toString(),
headers: {
"X-CSRFToken": token,
}
);
return dataResponse.statusCode;
}
However, using the inspector, I get an error indicating the Access-Control-Allow-Origin
header is missing when trying to get the token. So I add
views.py
def api_v1_token(request):
if request.method == "GET":
response = HttpResponse(get_token(request))
response["Access-Control-Allow-Origin"] = "http://localhost:8001"
return response
return HttpResponseBadRequest()
def api_v1_form(request: HttpResponse):
if request.method == "OPTIONS":
# ...
response["Access-Control-Allow-Origin"] = "http://localhost:8001"
return response
Now I can get the token, however the POST request outputs and error PreflightMissingAllowOriginHeader
. I get that I am supposed to add a header, but did I not already add Access-Control-Allow-Origin
on all requests ?
Moreover I see that for the POST request there are two requests : preflight and fetch, preflight returns 500 internal
error because django tries to parse the content of the empty request. That I can fix by adding a filter
views.py
if request.method == "OPTIONS": # preflight
res = HttpResponse()
res["Access-Control-Allow-Origin"] = "http://localhost:8001"
return res
if request.method == "GET":
# normal processing after that
And now preflight returns 200 OK
. But I am left with a CORS error on the fetch request (HeaderDisallowedByPreflightResponse
).
Now, I am stuck. I think there is something I don't understand about CORS headers and requests : am I missing headers ? with what values ? and is the app expected to process the data from the preflight and return the result in a subsequent GET request instead of a single POST request ? Is it the expected behavior ?