Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting a new XSRF-Token using useRequestHeaders after updating to Nuxt 3.8.0 #23968

Closed
ahoiroman opened this issue Oct 26, 2023 · 18 comments
Closed

Comments

@ahoiroman
Copy link

Environment

  • Operating System: Darwin
  • Node Version: v20.6.1
  • Nuxt Version: 3.8.0
  • CLI Version: 3.9.1
  • Nitro Version: 2.7.0
  • Package Manager: npm@9.8.1
  • Builder: -
  • User Config: modules, extends, imports, colorMode, devtools, app, runtimeConfig, routeRules, content, i18n, image, googleFonts, tailwindcss
  • Runtime Modules: @nuxt/ui@2.9.0-28293701.a8279d1, @nuxt/content@2.8.5, @nuxtjs/i18n@8.0.0-rc.5, @nuxt/image@1.0.0, @nuxtjs/google-fonts@3.0.2
  • Build Modules: -

Reproduction

As one needs a working Laravel API as backend to reproduce this, I cannot really provide a reproduction.

Describe the bug

I got an issue after upgrading to Nuxt 3.8.0

I am using this little util to consume my Laravel sanctum api:

import {$fetch, FetchError, type FetchOptions} from 'ofetch'

const CSRF_COOKIE = "XSRF-TOKEN"
const CSRF_HEADER = "X-XSRF-TOKEN"

// could not import these types from ofetch, so copied them here
interface ResponseMap {
    blob: Blob
    text: string
    arrayBuffer: ArrayBuffer
}

type ResponseType = keyof ResponseMap | "json"
// end of copied types

export type LarafetchOptions<R extends ResponseType> = FetchOptions<R> & {
    redirectIfNotAuthenticated?: boolean
    redirectIfNotVerified?: boolean
    useApiMiddleware?: boolean
    pick?: string[]
}

export async function $larafetch<T, R extends ResponseType = "json">(
    path: RequestInfo,
    {
        redirectIfNotAuthenticated = true,
        redirectIfNotVerified = true,
        useApiMiddleware = true,
        ...options
    }: LarafetchOptions<R> = {}
) {
    const {frontendUrl, apiVersion} = useRuntimeConfig().public
    const backendUrl = useApiMiddleware ? `${frontendUrl}/api/${apiVersion}` : `${frontendUrl}/web/`
    const router = useRouter()
    let token = useCookie(CSRF_COOKIE).value

    // on client initiate a csrf request and get it from the cookie set by laravel
    if (
        process.client &&
        ["post", "delete", "put", "patch"].includes(
            options?.method?.toLowerCase() ?? ""
        )
    ) {
        await initCsrf()
        token = useCookie('XSRF-TOKEN')?.value
    }

    let headers: any = {
        accept: "application/json",
        ...options?.headers,
        ...(token && {[CSRF_HEADER]: token}),
    }

    if (process.server) {
        headers = {
            ...headers,
            ...useRequestHeaders(["cookie"]),
            referer: frontendUrl,
        }
    }

    try {
        return await $fetch<T, R>(path, {
            baseURL: backendUrl,
            ...options,
            headers,
            credentials: "include",
            onResponse(test) {
                console.log(test.response)
            }
        })
    } catch (error) {
        if (!(error instanceof FetchError)) throw error

        // when any of the following redirects occur and the final throw is not caught then nuxt SSR will log the following error:
        // [unhandledRejection] Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
        const status = error.response?.status ?? -1

        if (redirectIfNotAuthenticated && [401, 419].includes(status)) {
            await router.push("/auth/signin")
        }

        if (redirectIfNotVerified && [409].includes(status)) {
            await router.push("/verify-email")
        }

        if ([500].includes(status)) {
            console.error("[Error]", error.data?.message, error.data)
        }

        throw error
    }
}

async function initCsrf() {
    const {frontendUrl} = useRuntimeConfig().public

    await $fetch("/csrf", {
        baseURL: frontendUrl,
    })
}

After upgrading to Nuxt 3.8.0, I am getting CSRF-errors, once process.server is true:

    if (process.server) {
        headers = {
            ...headers,
            ...useRequestHeaders(["cookie"]),
            referer: frontendUrl,
        }
    }

What happens: I am getting another XSRF-cookie with another cookie domain (not .example.test, but web.example.test).

Downgrading to Nuxt 3.7.4 solves the problem.

Additional context

No response

Logs

No response

@ahoiroman
Copy link
Author

Possibly related to #23900

@github-actions
Copy link
Contributor

Would you be able to provide a reproduction? 🙏

More info

Why do I need to provide a reproduction?

Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making.

What will happen?

If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritize it based on its severity and how many people we think it might affect.

If needs reproduction labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), we'll close them. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it.

How can I create a reproduction?

We have a couple of templates for starting with a minimal reproduction:

👉 https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz
👉 https://codesandbox.io/s/github/nuxt/starter/v3-codesandbox

A public GitHub repository is also perfect. 👌

Please ensure that the reproduction is as minimal as possible. See more details in our guide.

You might also find these other articles interesting and/or helpful:

@danielroe
Copy link
Member

This is likely not linked to #23900 unless you are using useFetch.

@ahoiroman
Copy link
Author

I created a reproducer:

Frontend: https://github.com/ahoiroman/csrf-frontend-reproducer
Backend: https://github.com/ahoiroman/csrf-backend-reproducer

I also created a small video showing the issue on my local instance of that reproducer.

I will also try to provide a public reproducer you all could use to play around.

Bildschirmfoto.2023-10-27.um.11.51.32.mp4

@ahoiroman
Copy link
Author

This video shows the behavior on running Nuxt 3.7.4 (there is no issue):

Bildschirmfoto.2023-10-27.um.12.06.06.mp4

@ahoiroman
Copy link
Author

This is likely not linked to #23900 unless you are using useFetch.

Yes, I reverted the changes of the change #23462 and the error is indeed still there. So there has to be another change that has been introduced in 3.8.0.

@UrbinCedric
Copy link

UrbinCedric commented Oct 30, 2023

Same problem here... A little problematic.

Thanks @ahoiroman, helped us a lot.

Lets hope we can resolve this 🙂!

@ahoiroman
Copy link
Author

Did my reproducer help, @danielroe ?

Anything I can do (with my limited possibilities) to help you out?

@ahoiroman
Copy link
Author

As this is still tagged with "needs reproduction" I'd like to ask, whether I can help with an live-example/a hosted version of the laravel instance?

@misbahansori
Copy link

Is there a temporary solution for it? I'm facing the same issue on production.

@danielroe
Copy link
Member

This should be resolved in v3.8.2 via #24333.

Let me know if not and I'll reopen.

@misbahansori
Copy link

@danielroe I just tested it, and I can confirm that the issue still exists.

@danielroe danielroe reopened this Dec 11, 2023
@yk1711
Copy link

yk1711 commented Jan 2, 2024

Also seeing this on 3.8.2. @ahoiroman did you manage to find a workaround ?

@ahoiroman
Copy link
Author

Unfortunately not, I am sorry.

@misbahansori
Copy link

Also seeing this on 3.8.2. @ahoiroman did you manage to find a workaround ?

I've managed to find a workaround. The key is to delete the CSRF cookie when there is an error.
My implementation looks like this, but I'm not sure if it's the best way to do it.
At least it works for me right now.

Screenshot 2024-01-03 at 14 42 41

@yk1711
Copy link

yk1711 commented Jan 3, 2024

Thank you @misbahansori and @ahoiroman for your research and ideas. For now, I made a workaround like this.

            if (
                process.client &&
                (document.cookie.match(/XSRF-TOKEN=/g)?.length ?? 0) > 1
            ) {
                document.cookie =
                    'XSRF-TOKEN=; expires=Thu, 01 Jan 1970 00:00:00 UTC;'
            }

This goes on top of onRequest interceptor. If we find 2 XSRF-TOKEN cookies in the cookie string - it means that Nuxt has set the wrong duplicate cookie. We remove it using the regular document.cookie browser api. It works for my case, because the cookie set by Laravel in my case is set to wildcard subdomain, like .myapp.com and the duplicate set by Nuxt is set directly to myapp.com. In this case since I'm not including domain in the document.cookie set call, it will always remove only the wrong nuxt cookie. I'm using the default browser document.cookie call instead of useCookie nuxt wrapper, since I can't be sure how useCookie is dealing with 2 cookies of the same name.

@tasiotas
Copy link

tasiotas commented Jan 8, 2024

This also prevents me from upgrading out of 3.7.x

@gontiago92
Copy link

This appears to be fixed on Nuxt 3.9.1, at least for us. No additional XSRF-TOKEN is being generated.


  • Operating System: Darwin
  • Node Version: v21.0.0
  • Nuxt Version: 3.9.1
  • CLI Version: 3.10.0
  • Nitro Version: 2.8.1
  • Package Manager: bun@1.0.23
  • Builder: -
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Backend

  • Laravel: 10.40
  • Sanctum: 3.3.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants