How to add partitioned attribute to cookies in Django ?

My backend is hosted on render and i was testing the email OTP functionality, but on making a GET request to “/register/” endpoint to get the “csrftoken” i am getting this:

Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.

null live.html:58:17

GET

http://127.0.0.1:8001/favicon.ico

[HTTP/1 404 File not found 0ms]

Cookie warnings 2

Cookie “csrftoken” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute. register

Cookie “csrftoken” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute. register

{"message": "CSRF token set."}

views.py:

@ensure_csrf_cookie
def register(request):
    if request.method == "POST":
        # email = request.POST.get("email")
        try:
            # Parse the JSON body
            data = json.loads(request.body)
            email = data.get("email")
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "message": "Invalid JSON format."})

        try:
            validate_email(email)
        except ValidationError:
            return JsonResponse({"success": False, "message": "Invalid email format."})

        email_otp = generate_otp()
        redis_key = f"otp:{email}"

        cache.set(redis_key, email_otp)

        try:
            message = BaseEmailMessage(
                template_name="emails/otp_template.html",
                context={"email_otp": email_otp},
            )
            message.send([email])
        except (BadHeaderError, SMTPException) as e:
            return JsonResponse(
                {"success": False, "message": f"Failed to send OTP. Error: {str(e)}"}
            )

        return JsonResponse(
            {
                "success": True,
                "message": "OTP sent successfully. Please check your email.",
            }
        )

    return JsonResponse({"message": "CSRF token set."})


def verify_otp(request):
    if request.method == "POST":
        # email = request.POST.get("email")
        # user_otp = request.POST.get("otp")
        try:
            # Parse the JSON body
            data = json.loads(request.body)
            email = data.get("email")
            user_otp = data.get("otp")
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "message": "Invalid JSON format."})

        if not email or not user_otp:
            return JsonResponse(
                {"success": False, "message": "Email and OTP are required."}
            )

        redis_key = f"otp:{email}"
        stored_otp = cache.get(redis_key)

        if stored_otp is None:
            return JsonResponse(
                {"success": False, "message": "OTP expired or not found."}
            )

        if validate_otp(stored_otp, user_otp):
            cache.delete(redis_key)
            return JsonResponse(
                {"success": True, "message": "OTP verified successfully."}
            )
        else:
            return JsonResponse({"success": False, "message": "Invalid OTP."})

    return JsonResponse({"success": False, "message": "Invalid request method."})

live.html (http://127.0.0.1:8001/live.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Register and Verify OTP</title>
</head>
<body>

    <h1>Register</h1>

    <!-- Email Form -->
    <form id="registerForm" method="POST" action="https://shop-sphere-app.onrender.com/register/">
        <label for="email">Email: </label>
        <input type="email" id="email" name="email" required>
        <button type="submit">Send OTP</button>
    </form>

    <!-- OTP Verification Form (Initially hidden) -->
    <div id="otpForm" style="display: none;">
        <h2>Enter OTP</h2>
        <form id="verifyOtpForm" method="POST" action="https://shop-sphere-app.onrender.com/verify_otp/">
            <label for="otp">OTP: </label>
            <input type="text" id="otp" name="otp" required>
            <button type="submit">Verify OTP</button>
        </form>
        <div id="otpMessage"></div>
    </div>

    <div id="errorMessage" style="color: red;"></div>

    <script>
        // Fetch CSRF token by making a GET request to Django
        fetch("https://shop-sphere-app.onrender.com/register/", {
            method: "GET",
            credentials: "include",
        })
        .then(response => response.text())
            .then((res) => console.log(res));

         // Function to get CSRF token from cookies
        function getCookie(name) {
            let cookieValue = null;
            if (document.cookie && document.cookie !== "") {
                const cookies = document.cookie.split(";");
                for (let i = 0; i < cookies.length; i++) {
                    const cookie = cookies[i].trim();
                    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }

        const csrftoken = getCookie("csrftoken");
        console.log(csrftoken);

        // Handle email registration form submission
        document.getElementById("registerForm").addEventListener("submit", function(event) {
            event.preventDefault(); // Prevent form submission
            let email = document.getElementById("email").value;
            fetch("https://shop-sphere-app.onrender.com/register/", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "X-CSRFToken": csrftoken, // Include CSRF token in the headers
                },
                credentials: "include",
                body: JSON.stringify({ email: email }),
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    // Show OTP form on successful OTP sent
                    document.getElementById("otpForm").style.display = "block";
                    document.getElementById("errorMessage").textContent = ""; // Clear any previous error messages
                } else {
                    // Show error message if OTP wasn't sent successfully
                    document.getElementById("errorMessage").textContent = data.message;
                }
            })
            .catch(error => {
                console.log(error);
                document.getElementById("errorMessage").textContent = "An error occurred while sending OTP.";
            });
        });

        // Handle OTP verification form submission
        document.getElementById("verifyOtpForm").addEventListener("submit", function(event) {
            event.preventDefault(); // Prevent form submission
            let email = document.getElementById("email").value;
            let otp = document.getElementById("otp").value;

            fetch("https://shop-sphere-app.onrender.com/verify_otp/", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "X-CSRFToken": csrftoken, // Include CSRF token in the headers
                },
                credentials: "include",
                body: JSON.stringify({ email: email, otp: otp }),
            })
            .then(response => response.json())
            .then(data => {
                const otpMessageDiv = document.getElementById("otpMessage");
                if (data.success) {
                    otpMessageDiv.style.color = "green";
                    otpMessageDiv.textContent = data.message;
                } else {
                    otpMessageDiv.style.color = "red";
                    otpMessageDiv.textContent = data.message;
                }
            })
            .catch(error => {
                document.getElementById("otpMessage").textContent = "An error occurred during OTP verification.";
            });
        });
    </script>

</body>
</html>

prod.py:

CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8001"]

CSRF_COOKIE_SAMESITE = "None"
CSRF_COOKIE_SECURE = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
    "http://127.0.0.1:8001",
    "http://localhost:8001",
]

From what I can see, direct support for this is not yet implemented in Django.

Additionally, it’s not even a standard yet - which means it could undergo changes before being ratified. In fact, it looks like work on this has stalled, the last draft expired almost 2 years ago. I also can’t find any email in the IETF working group archives since 2022 about it. (Granted, this is a rather cursory search.)

See draft-cutler-httpbis-partitioned-cookies-01 - Cookies Having Independent Partitioned State specification

IF these are going to become “mandatory”, I can’t see it happening anytime soon.

Is there any way to work around this ?

Work around what?

What is generating that message? (That’s not Django producing that.)

Yeah i know. I meant any work around for the browser error ?

What browser error? I see a couple of warnings in what you posted, but that’s about it.

Cookie warnings 2

Cookie “csrftoken” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute. [register](https://shop-sphere-app.onrender.com/register/)

Cookie “csrftoken” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.

this warning is now allowing the csrftoke to be set in the cookies.

What browser are you using?

i am using fireox (OS: Arch)

Then I would look for a setting in Firefox to not check this. If Firefox is rejecting the cookies because of this, then it’s a problem with Firefox.

Okay, i will see what i can do. Thnx as always

Well i marked the cookie as partitioned by including a custom middleware from this stackoverflow answer: [Marking a Cookie as Partitioned in Django - Stack Overflow]

Then i tried reading the cookie but it kept turning up “null”, when i checked it under dev tools it was there. since “HttpOnly” is not set it could only be that the browser is restricting cross-site cookies.
So what i was thinking of doing is this:

make a URL that would get the cookie and return “csrftoken” in response header (since cookie cant be read cross site):

@ensure_csrf_cookie
@require_http_methods(['GET'])
def set_csrf_token(request):
    response = JsonResponse({"message": "GET request handled"}, status=200)
    response["X-CSRFToken"] = request.META.get("CSRF_COOKIE", "")
    return response

Question is, is this a secure way ? Is there any other way to do this ?

Yes.

Call the get_token() method that is in django.middleware.csrf to get a token. Include that token in your JSON Response. Retrieve the token from that response in your JavaScript.

Okay

Is including the token in response header safe because i am planning to get this token (on the frontend), save it and use it in subsequent requests ?

Not necessarily universally.

There are situations - logging in is one of them - where the tokens are changed.

Also, keep in mind that the CSRF_COOKIE is not the same thing as the csrf_token. The docs explain why you shouldn’t just use the cookie as the token.

Again, I strongly encourage you to read the entire docs at Cross Site Request Forgery protection | Django documentation | Django and How to use Django’s CSRF protection | Django documentation | Django for a complete understanding of how all these things work - and adopt the principles of what they’re showing in the examples rather than trying to find your own shortcuts.