1

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
kmdreko
  • 42,554
  • 6
  • 57
  • 106
Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22
  • please share where and _how_ you are setting the cookie – Daniel A. White Jun 07 '22 at 13:04
  • @DanielA.White it's in the `set-cookie` header. would the source code where I build the response be more helpful than the response HTTP headers? – Jeremy Meadows Jun 07 '22 at 13:06
  • you are marking the cookie as `Secure` but requesting this over `http`, not `https` – Daniel A. White Jun 07 '22 at 13:09
  • @DanielA.White crap, it ended up that way trying to satisfy all the CORS errors, with the `SameSite` requiring `Secure` and whatnot... unfortunately I can't test it until I can get back on my desktop, but then why does the cookie get set without a redirect? – Jeremy Meadows Jun 07 '22 at 13:16
  • @DanielA.White I upgraded my server to TLS and changed the protocol used by `fetch`, but it doesn't change the behavior at all. It looks like it disappears on any sort of page refresh too, as opposed to just redirects. – Jeremy Meadows Jun 07 '22 at 21:42

1 Answers1

1

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 :)

Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22