nuxt icon indicating copy to clipboard operation
nuxt copied to clipboard

accessing `useCookie` on server immediately after setting it doesn't return cookie

Open sync42johnny opened this issue 2 years ago • 13 comments

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:

  1. On accessing the index page (index.vue), the confirm middleware is invoked. This middleware tries to set the authToken cookie on the server-side.
  2. The middleware then redirects to /main.
  3. The /main page is protected by the auth middleware, which checks for the presence of the authToken cookie.
  4. Due to the cookie not being recognized (or initialized correctly on the server), the auth middleware 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

sync42johnny avatar Aug 14 '23 11:08 sync42johnny

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.

danielroe avatar Aug 14 '23 11:08 danielroe

did you find a workaround? im having a similar issue where changing useCookie value is not possible on server (inside a plugin)

saifobeidat avatar Dec 14 '23 13:12 saifobeidat

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

saifobeidat avatar Jan 13 '24 10:01 saifobeidat

@danielroe Hey, sorry for pinging you but any chances that this issue will be resolved soon? Seems like a critical one

vhovorun avatar Feb 22 '24 09:02 vhovorun

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,
    }
  };
});

RomanSkrypnik avatar Mar 14 '24 13:03 RomanSkrypnik

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

mlbonniec avatar Apr 25 '24 12:04 mlbonniec

@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.

BobbieGoede avatar Sep 12 '24 20:09 BobbieGoede

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'.

danielroe avatar Sep 13 '24 12:09 danielroe