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
相关产品推荐
相关产品推荐

