You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt3中Laravel Sanctum CSRF令牌校验Composable异常排查

Nuxt3 + Laravel Sanctum CSRF令牌获取问题排查

我正在开发一个以Laravel 9为后端API(搭配Sanctum和Fortify)的Nuxt 3应用,编写了一个Composable用于请求Laravel Sanctum的/sanctum/csrf-cookie路由获取CSRF令牌,但通过console.log()打印该Composable的状态时,始终显示空Proxy错误。

Composable文件auth.ts:

export function useCsrfToken() {
  const baseURL = useRuntimeConfig().public.LARAVEL_BASE_URL
  const options = {
    baseURL,
    credentials: 'include',
    headers: {
      Accept: 'application/json',
    } as HeadersInit,
    method: 'GET',
  }

  const state = ref({
    status: '',
    error: {},
  })

  async function getCsrfToken() {
    state.value.error = {}
    state.value.status = ''
    await $fetch('/sanctum/csrf-cookie', options)
      .then(() => (state.value.status = 'ok'))
      .catch((error) => {
        console.log('error from useCsrfToken', error)

        state.value.error = { error }
      })
  }

  getCsrfToken()

  return { state }
}

登录页面代码:

<script setup>
definePageMeta({
  layout: false,
})

const submit = () => {
  form.processing = true
  const { state } = useCsrfToken()

  console.log('error from login', state.value.error)
  // console.log('status from login', status)
}

const form = reactive({
  email: '',
  password: '',
  remember: false,
  processing: false,
  errors: {},
})
</script>

<template>
  <div>
    <!-- <TheTwoFactorChallenge v-if="verification" @2fapassed="submit" /> -->
    <NuxtLayout name="auth">
      <template #title>
        <p class="card-header-title">Inicie Sesión</p>
      </template>

      <form novalidate @submit.prevent="submit">
        <div class="field">
          <label class="label" for="email">Usuario</label>
          <div class="control has-icons-left has-icons-right">
            <AppInput
              type="email"
              id="email"
              v-model="form.email"
              :error="form.errors?.email"
              autocomplete="email"
              required
            />
            <AppIconLeft icon="fa-solid fa-envelope" />
            <AppIconError v-if="form.errors?.email" class="is-right" />
          </div>
          <AppHelpError :errors="form.errors?.email" />
        </div>

        <div class="field">
          <label class="label" for="password">Contraseña</label>
          <div class="control has-icons-left has-icons-right">
            <AppInput
              type="password"
              id="password"
              v-model="form.password"
              :error="form.errors?.password"
              autocomplete="new-password"
              required
            />
            <AppIconLeft icon="fa-solid fa-lock" />
            <AppIconError v-if="form.errors?.password" class="is-right" />
          </div>
          <AppHelpError :errors="form.errors?.password" />
        </div>

        <div class="field">
          <div class="control">
            <AppSwitch
              id="remember"
              v-model:checked="form.remember"
              class="is-small is-link"
            />
            <label for="remember">Recuérdame</label>
          </div>
        </div>

        <div class="is-flex is-justify-content-flex-end mb-4">
          <NuxtLink to="#" class="has-text-link">
            ¿Olvidó su contraseña?
          </NuxtLink>
        </div>

        <div class="field">
          <div class="control">
            <AppButton
              class="is-link is-fullwidth"
              type="submit"
              :is-loading="form.processing"
              >Entrar</AppButton
            >
          </div>
        </div>
      </form>
    </NuxtLayout>
  </div>
</template>

控制台输出:

控制台输出截图


问题排查与解决方案

1. 异步操作未等待,状态未更新

你在submit函数中调用useCsrfToken()后立刻打印状态,但getCsrfToken()是异步函数,此时请求还未完成,状态仍为初始的空Proxy。

修复方式:

  • 将submit改为异步函数,等待CSRF请求完成后再访问状态:
const submit = async () => {
  form.processing = true
  const { state, getCsrfToken } = useCsrfToken()
  await getCsrfToken() // 等待CSRF请求完成
  console.log('error from login', state.value.error)
}
  • 修改Composable,移除内部自动调用getCsrfToken()的逻辑,由调用方控制执行时机,避免重复请求:
export function useCsrfToken() {
  const baseURL = useRuntimeConfig().public.LARAVEL_BASE_URL
  const options = {
    baseURL,
    credentials: 'include',
    headers: {
      Accept: 'application/json',
    } as HeadersInit,
    method: 'GET',
  }

  const state = ref({
    status: '',
    error: null,
  })

  async function getCsrfToken() {
    state.value.error = null
    state.value.status = ''
    try {
      await $fetch('/sanctum/csrf-cookie', options)
      state.value.status = 'ok'
    } catch (error) {
      console.log('error from useCsrfToken', error)
      state.value.error = error
    }
  }

  return { state, getCsrfToken }
}

2. Ref嵌套对象的响应性问题

用ref包裹对象时,虽然state.value是响应式的,但直接修改内部属性可能导致响应式追踪延迟,建议改用reactive定义状态:

const state = reactive({
  status: '',
  error: null,
})
// 修改时直接赋值
state.error = error

这样在访问状态时不会出现空Proxy问题。

3. 跨域配置检查

确保Laravel .env 配置正确的前端域名:

SANCTUM_STATEFUL_DOMAINS=localhost:3000
SESSION_DOMAIN=.localhost

同时在Nuxt的nuxt.config.ts中配置跨域或代理:

export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      LARAVEL_BASE_URL: 'http://localhost:8000'
    }
  },
  nitro: {
    devProxy: {
      '/sanctum': {
        target: 'http://localhost:8000',
        changeOrigin: true,
        prependPath: true
      }
    }
  }
})

4. 控制台打印的正确姿势

对于响应式对象,直接打印可能显示Proxy,可通过以下方式查看实际值:

console.log('error from login', JSON.parse(JSON.stringify(state.value.error)))

内容的提问来源于stack exchange,提问作者Dovahkiin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 06:55:22