accessing `useCookie` on server immediately after setting it doesn't return cookie
Environment
Nuxi 3.6.5
- Operating System: Darwin
- Node Version: v16.15.1
- Nuxt Version: 3.6.5
- Nitro Version: 2.5.2
- Package Manager: [email protected]
- Builder: vite
- User Config: devtools
- Runtime Modules: -
- Build Modules: -
Reproduction
https://github.com/sync42johnny/nuxt-app
Describe the bug
Description:
An issue has been identified with the authentication logic in the middleware of a Nuxt application. The application attempts to authenticate a user by checking for an authToken cookie. However, due to the authToken cookie not being set on the server-side before navigateTo, the middleware encounters an infinite redirection loop.
Details:
Affected Files:
-
index.vue -
main.vue -
app.global.ts -
auth.ts -
confirm.ts
Workflow:
- On accessing the index page (
index.vue), theconfirmmiddleware is invoked. This middleware tries to set theauthTokencookie on the server-side. - The middleware then redirects to
/main. - The
/mainpage is protected by theauthmiddleware, which checks for the presence of theauthTokencookie. - Due to the cookie not being recognized (or initialized correctly on the server), the
authmiddleware redirects the user back to/, leading to a continuous loop of redirections.
Additional context
The issue might be related to the incorrect functioning of the useCookie function when used within middleware before navigateTo.
Logs
authToken.value in app.global undefined 2:10:58 PM
in confirm 2:10:58 PM
authToken.value in app.global undefined 2:10:58 PM
authToken in auth undefined
The issue here is that when setting a cookie it is set in the response headers. But when reading a cookie it is looked for in the request headers.
When you redirect to /main, the middleware for main is immediately called and the cookie hasn't yet been set, because it's still in the outgoing response, not the incoming.
If your intent is to check for the cookie immediately after it is initially set (that is, in the same request) then you probably also need to set it in the event.context, for example:
if (process.server) {
// you can access arbitrary values on the event, like this
useRequestEvent().context.token
}
There is a PR to do this automatically: https://github.com/nuxt/nuxt/pull/21940.
did you find a workaround? im having a similar issue where changing useCookie value is not possible on server (inside a plugin)
here is my case and how i fixed it..
i have 2 plugins:
The first plugin: startup.ts it reads if the url has a token in the url, if yes, it sets it in a cookie and immediately calls an api (fetch user info) -- this is on server
something like this:
// startup.ts
if (route.query.access_token) {
useCookie<any>("access-token").value="the token"
}
if( useCookie<any>("access-token").value) {
await fetchUser(); // this gets executed (which means the token is stored correctly in the cookie)
}
the second plugin: api.ts has some stuff for managing api calls like the above, and interceptors, and getting headers as well..
something like this:
// api.ts
const getHeaders = function (sendToken: boolean, extraHeaders = {}) {
let headers: Headers = {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
platform: "web",
"x-trace-id": userAnalyticsID.value.toString(),
...extraHeaders
};
if (sendToken && useCookie<any>("access-token").value )) {
headers["x-access-token"] = accessTokenCookie.value;
}
}
the getHeaders was called by the fetchUser() to get the headers dynamically, the main issue here... the useCookie<any>("access-token").value ) has no value, which means the headers won't contain the access-token so it will fail..
the weirdest thing, is the `fetchUser() is wrapped with if statement to check if we have a token cookie, that's why it got called, but why that cookie was not there in the second plugin?
My fix, I followed @danielroe method above, by saving the token in event.context in the first plugin
if(process.server)
{
const event = useRequestEvent();
event.context["token"] = route.query.access_token
}
and then read it as well in the second plugin
const event = useRequestEvent();
let tkn = event.context.token
it works, but im not really confident, and hopefully the root cause gets fixed
@danielroe Hey, sorry for pinging you but any chances that this issue will be resolved soon? Seems like a critical one
Probably it's a little bit late but here is workaround:
import type { CookieOptions } from 'nuxt/dist/app/composables';
export default defineNuxtPlugin(() => {
const setMyCookie = (key: string, val = '', options: CookieOptions = {}) => {
useCookie(key, options).value = val;
if (process.server) {
const exp = options.expires;
if (exp && exp.toUTCString) {
options.expires = exp.toUTCString();
}
val = encodeURIComponent(val);
let updatedCookie = key + '=' + val;
for (const key in options) {
updatedCookie += '; ' + key;
const propValue = options[key];
if (propValue !== true) {
updatedCookie += '=' + propValue ;
}
}
const event = useRequestEvent();
event.node.req.headers.cookie = event.node.req.headers.cookie
? `${updatedCookie}; ${event.node.req.headers.cookie}`
: updatedCookie;
}
};
return {
provide: {
setMyCookie,
}
};
});
Hi, is there any news on this issue ?
It seems that some methods has been implemented on h3 https://github.com/unjs/h3/issues/509
@danielroe does this mean we want useCookie to read and write to both Cookie (req) and Set-Cookie (res) headers server-side? I could implement this in the i18n module for https://github.com/nuxt-modules/i18n/issues/2975 or see if I can revive/update https://github.com/nuxt/nuxt/pull/21940 to have it work from Nuxt's side.
Yes, it would be good to do that within Nuxt.
We would need to have the initial value of useCookie coming from the request headers, then (once set) future calls would access this value/update the value in the response headers.
One tricky thing is that this very much depends on cookie options - these effectively mean we can have different cookies which need to be tracked separately but have the same 'name'.