djangoposthttp-status-code-403django-csrf

How do I setup CSRF token for purely REST API communication between two Django applications?


I have two separate backends both built with Django that I want to communicate with each other. Requests to the GET endpoints worked fine but POST requests gave me the following error:

Forbidden (CSRF cookie not set.): /endpoint/path/

And the instructions from Django mention setting {% csrf_token %} in "any template that uses a POST form". But I have no templates! It's just an API.

How do I get around this error?


Solution

  • One "solution" is to just remove the "django.middleware.csrf.CsrfViewMiddleware" middleware from your settings.py but that is not recommended.

    There is actually another way to pass the csrf token to your API response without the need for a POST form. The Django docs just do not have an example for this for some reason. It is simply by calling django.middleware.csrf.get_token(). Here's an example.

    Server 1 - Requiring csrf token ("Server")

    # In a views.py
    from django import http
    from django.middleware.csrf import get_token
    ...
    @http.require_safe
    @cache.never_cache
    def get_csrf(request):
        # This call will automatically add the token to the response!
        get_token(request)
        return http.HttpResponse("hello world")
    
    
    @http.require_POST
    @cache.never_cache
    def post_data(request):
        msg = "received POST request"
        return http.HttpResponse(msg)
    

    Server 2 - Making requests ("Client")

    # Also in a views.py
    import requests
    ...
    URL = "https://url.to.server/"
    
    def update_session(session, response):
        response.raise_for_status()
        session.cookies.update(response.cookies)
        session.headers["x-csrftoken"] = response.cookies["csrftoken"]
    
    @http.require_GET
    @cache.never_cache
    def test_api(request):
        # You will want to create a session and store the cookie so it can be easily reused
        session = requests.Session()
        csrf_response = session.get(f"{URL}get_csrf/")
        update_session(session, csrf_response)
        # The previously problematic POST request should now work
        response = session.post(f"{URL}post_data/")
    
        # `response.text` should now successfully contain "received POST request" 
        return http.HttpResponse(response.text)