Search code examples
javascripthttpcookiesrustrust-axum

cookies getting deleted after automatic redirect


I have a log-in page which makes a request to my server, which sets a session cookie and redirects to a dashboard page. On the log-in page, I can verify the cookie gets set correctly after sending credentials, and it will persist when I manually click on links to navigate around the site (as expected).

However, when I try to automatically redirect to the dashboard after the log-in function succeeds, my cookie is unset and I cannot figure out where it has gone. I have tried managing the redirect via document.location/window.location with the different .href/.path/.url fields, but the cookie is gone after calling any of those or refreshing the page.

I'm using self-signed HTTPS 127.0.0.1 with CORS (I am no longer receiving CORS errors after lots of headache, so I don't think the problem is there, especially since I can see it exists without the redirect). Also, I'm using Svelte as the front-end and Axum (Rust) as my back-end, if that's important at all.

Why does my cookie get "eaten" when trying to force-redirect the user to another page?

request method:

await fetch('https://localhost:4000/users', {
    method: 'POST',
    credentials: 'include',
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    },
    body: `{ "email": "${email}", "password": "${await hash}" }`
});

// cookie exists if this line is removed
document.location.href = '/dashboard';
// cookie is gone when the new page loads

server config (Axum):

let server = axum_server::bind_rustls(
    SocketAddr::from(([127, 0, 0, 1], 4000)),
    tls_config,
)
.serve(
    Router::new()
        .route("/", get(root))
        .layer(
            CorsLayer::new()
                .allow_headers([
                    header::CONTENT_TYPE,
                    header::CONTENT_LENGTH,
                    header::COOKIE,
                    header::SET_COOKIE,
                ])
                .allow_methods([
                    Method::GET,
                    Method::HEAD,
                    Method::OPTIONS,
                    Method::DELETE,
                    Method::POST,
                ])
                .allow_origin([
                    HeaderValue::from_str("http://127.0.0.1:3000").unwrap(),
                    HeaderValue::from_str("https://127.0.0.1:3000").unwrap(),
                ])
                .allow_credentials(true),
        )
        .into_make_service(),
);

response headers:

HTTP/2 200 OK
content-type: application/json
set-cookie: session=1234; Path=/; HttpOnly; SameSite=None; Secure
content-length: 26
access-control-allow-origin: https://127.0.0.1:3000
access-control-allow-credentials: true
vary: origin
vary: access-control-request-method
vary: access-control-request-headers
date: Tue, 07 Jun 2022 01:56:11 GMT

Solution

  • I ended up replacing my fetch calls with axios calls and it works as expected now. I still don't know if I was using it wrong, but these look like they'd be pretty identical to me...

    await fetch('https://localhost:4000/users', {
        method: 'POST',
        credentials: 'include',
        headers: {
            'Content-Type': 'application/json;charset=utf-8'
        },
        body: `{ "email": "${email}", "password": "${await hash}" }`
    });
    
    await axios.post('https://localhost:4000/users', {
        email: email,
        password: await hash,
    }, {
        withCredentials: true
    });
    

    If somebody is able to explain what the difference is, I'd be super interested :)