0

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 ?

4
  • Note about the duplicate: The error message in your browsers console will tell you about the specific header you need to allow in the Access-Control-Allow-Headers header (probably the CSRF token header) Commented Mar 26 at 11:39
  • I added the Access-Control-Allow-Headers header with Content-Type,X-CSRFToken,x-csrftoken,csrftoken in the preflight response, and Access-Control-Allow-Methods with POST,GET,OPTIONS methods. However the actuel fetch request returns a 403 Forbidden. Forbidden (CSRF cookie not set.): /api/v1/form. CSRF_COOKIE_SECURE =False so I'm not sure what else is wrong Commented Mar 27 at 8:22
  • That's a different issue, CORS and CSRF are different things. So the problem you described with CORS is solved Commented Mar 27 at 9:57
  • About the CSRF error that's because you're making a cross site request so the cookies are not sent when making the request by default. Check the added duplicate target, you need to ensure withCredentials is set to true in both the request that sets the CSRF cookie and the request that needs the cookie to be set. Commented Mar 27 at 10:13

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.