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!
2
Answers
Browser security policies can block cookies from/to ajax requests. See
For a cross-origin API, I’d rather not use cookies at all. Imagine any API client other than a browser! Cookies are meant to track browsing sessions in a website, not storing state in arbitrary http clients.
Just include the refresh token in the response body and store it in your app state as well.
I’ve implemented many solutions of this type, where the cookie is used to enable an API message credential and is hence issued on the API side of the architecture.
It looks to me like your problem is the
expires
property of the cookie. This causes the cookie to be persistent and I expect it is set incorrectly. Everything else looks right.I think you might fix your problem if you remove that cookie property. Doing do is also a security best practice and results in a session cookie that is removed when the browser is closed.
Also, it is not portable to make assumptions about refresh token formats or payloads. Instead, a refresh token is considered expired when you try to use it at the authorization server’s token emdpoint and receive a response with an
invalid_grant
error code. Therefore, when using cookies that represent tokens, always derive expiry from the underlying token.