Search code examples
reactjsdjangocookies

Browser Not Saving HttpOnly Cookie React/Django


I have a netlify frontend at app.mydomain.com, and a django ninja REST API at api.mydomain.com. When I submit to my login endpoint, the api returns successfully with the access key (which I store in app state) and a refresh token in the form of a secure, httponly cookie. I can see this cookie is returned by looking in the response headers in dev tools. The cookie however is not stored by the browser at all, I've gone through numerous other questions/answers and I believe I have implemented everything required, but it's still not working.

My login API call from the frontend is:

await fetch(
  AUTH_URL_OBTAIN,
  {
    method: RequestMethod.POST,
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({username: formData.email, password: formData.password}),
    credentials: "include",
  },
);

On the backend, the cookie is set like this:

response.set_cookie(
    key="refresh",
    value=refresh_token,
    expires=datetime.fromtimestamp(refresh_token_payload["exp"], timezone.utc),
    httponly=True,
    samesite="none",
    secure=True,
    path="/api/auth/web/token-refresh",
    domain=".mydomain.com",
)

I also have the following settings set (substituting the values in for environment variables):

CSRF_TRUSTED_ORIGINS = ["https://app.mydomain.com"]
CORS_ALLOWED_ORIGINS = ["https://app.mydomain.com"]
CORS_ORIGIN_WHITELIST = ["https://app.mydomain.com"]
CORS_ALLOW_CREDENTIALS = True

The login response provides the access token (which works as expected - I am able to make API calls using this just fine, and have credentials: include on all fetch requests) and the response headers are below:

Access-Control-Allow-Credentials: true

Access-Control-Allow-Origin: https://app.mydomain.com

Content-Length: 661

Content-Type: application/json; charset=utf-8

Cross-Origin-Opener-Policy: same-origin

Date: Thu, 06 Jun 2024 14:06:22 GMT

Referrer-Policy: same-origin

Server: daphne

Set-Cookie: refresh=ey...3uQ; Domain=.mydomain.com; expires=Sat, 06 Jul 2024 14:06:22 GMT; HttpOnly; Max-Age=2592000; Path=/api/auth/web/token-refresh; SameSite=none; Secure

Vary: origin

X-Content-Type-Options: nosniff

X-Frame-Options: DENY

I'm at a bit of a loss at this point - any advice would be very appreciated, thank you!


Solution

  • It has nothing to do with either Django or React. It is the way how you set the cookie.

    1. A cookie with a path can be set anywhere but accessible only on that path and its child. So you can only see the cookie when visiting the page /api/auth/web/token-refresh
    2. Consider using Max-Age over Expires since it is less error-prone. This is the quote from MDN page:

    Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and takes precedence when both are set. The rationale behind this is that when you set an Expires date and time, they're relative to the client the cookie is being set on. If the server is set to a different time, this could cause errors.

    1. Secure cookie can only be set through https or localhost