简体   繁体   中英

Why is the csrf cookie set when sending POST request to localhost:8000, but not when sending POST request 127.0.0.1:8000?

Why is the csrf cookie set when sending POST request to localhost:8000, but not when sending POST request 127.0.0.1:8000? Django then complains that the CSRF cookie is not set. (Assuming I open the frontend using localhost:3000, then same phenomenon occurs when opening the frontend script using 127.0.0.1:3000 but this time POST requests go through only to 127.0.0.1:8000). Basically, I'm interested on how to configure things in order to be able later on to serve the frontend (React in my case) and the backend (Django) from two different domains. For now I have no login feature etc. so CSRF protection makes no sense. However I'm interested, why with the current configuration, I'm not able to do cross origin requests (POST) with the CSRF token being in the header.

So here is my code:

Frontend:

export async function post_request_to_backend_with_csrf(url : string, data: {[key: string] : string}, headers: AxiosRequestHeaders | undefined = undefined) : Promise<AxiosResponse<any, any>> {
    // get csrf token
    var csrftoken = getCookie('csrftoken');
    if (csrftoken === null) {
        await getCSRFToken().then(
            (response) => {
                csrftoken = getCookie('csrftoken');
            }
        ). catch((e) => console.log(e))
    }
    var headers_arg : AxiosRequestHeaders = {'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': csrftoken!, 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'};
    for (let key in headers) {
        if (!(key in headers_arg)) {
            headers_arg[key] = headers[key];
        }
    }
    return axios.post(
        url, 
        data, 
        { // 
        withCredentials: true, // send cookies, authorization headers or TLS client certificates cross -origin.
        headers: headers_arg // contains CSRF cookie among others.
        });
}

function getCSRFToken() {
    return axios.get(CSRF_URL, {withCredentials: true});
}

Backend:

# settings.py
# ...

######## CORS SETUP ###########

CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_HEADERS = [
    # defaults
    "accept",
    "accept-encoding",
    "authorization",
    "content-type",
    "dnt",
    "origin",
    "user-agent",
    "x-csrftoken",
    "x-requested-with",
    # added
    "X-CSRFTOKEN",
    'access-control-allow-origin',
]
CORS_EXPOSE_HEADERS = [] + CORS_ALLOW_HEADERS

CORS_ALLOW_METHODS = [
    "DELETE",
    "GET",
    "OPTIONS",
    "PATCH",
    "POST",
    "PUT",
]

######## END of CORS SETUP ####


######## CSRF SETUP ###########
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CSRF_TRUSTED_ORIGINS=["http://localhost:3000", "http://127.0.0.1:3000"]
####### END CSRF SETUP ########

In summary I tried to implement: https://docs.djangoproject.com/en/4.0/ref/csrf/#:~:text=%3C/script%3E-,Setting%20the%20token%20on%20the%20AJAX%20request,-%C2%B6 but for cross-origin requests, where the last part doesn't work.

Update:

By taking a closer look, I realized that basically when requesting the csrf-token via a cross-origin request, the cookie is obtained in the response. Ie the header of the response contain Set-Cookie: csrftoken: "...", however it is not set (I cannot find it in Developer Tools --> Storage --> Cookies) and hence not accessible for the JS. I wonder if the issue is that localhost is accessed by insecure requests...

By looking more exactly at the browser setup, I found out the following things.

For the cross-origin request: localhost --> 127.0.0.1, the cookie (can see it by clicking on the (i) in Chrome left of the URL) was set but not accessible for JavaScript (not in DevTools > Storage > Cookies). Cookies that come from a different origin can never be read/accessed by JavaScript.

Now, the CSRF token protects by guaranteeing that the token and the cookie match and that only valid origins may access the token. Since cookies of a different origin cannot be read by JavaScript, the CSRF token must be obtained directly from the server, using the Django template {% csrf_token %} for example. Here, the CORS policy makes sure that no attacker is able to get the CSRF token, by blocking the attackers requests, ie by blocking all requests that don't come from the valid frontend. This CORS policy is enforced by the browser, so a malicious app could still retrieve then CSRF token, but since CSRF attacks can only happen in a browser, this again is not a problem.

Obviously, one should change

CORS_ALLOW_ALL_ORIGINS = True

to

CORS_ALLOWED_ORIGINS = [
    "http://127.0.0.1:3000",
    "http://localhost:3000",
    # frontend domain
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM