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

sidebase nuxt auth凭证认证异常求助:数据库请求成功但会话未授权

Sidebase Nuxt Auth 认证异常排查

我在应用中使用sidebase nuxt auth组件,通过axios对接外部API完成认证流程。API请求无报错且返回成功结果,但sidebase auth认证出现异常:认证逻辑返回true的同时,页面跳转到授权错误页,且index.vue中useSession()返回的状态为未授权。


相关代码

index.vue(表单定义文件)

<template>
  <section class="agents-enter">
    {{status}}
    <div class="container-fluid">
      <div class="row">
        <div class="col-12 col-lg-6 col-xl-7 d-flex flex-column">
          <SectionTitle
              title="Партнерам"
              subtitle="Приглашаем агентов к сотрудничеству по продаже автобусных билетов"
              description="Описание о том, что это и как это работает"
          />
          <!-- <img v-if="!isMobile()" class="image-section" alt="Агенты" src="/img/agents/agents.svg"> -->
        </div>
        <div class="col-12 col-lg-6 col-xl-5 d-flex justify-content-end">
          <div class="login-form-wrapper">
            <div class="login-form">
              <h3 class="title-form text-center">
                Вход для агентов
              </h3>
              <form @submit.prevent="signIn('credentials', { username: userStore.username, password: userStore.password })">
                <!--                      TODO добавить is-ok-bordered или is-error-bordered для валидации-->
                <div class="form-group">
                  <label for="login" class="form-label">Эл. почта</label>
                  <div class="position-relative">
                    <input v-model="userStore.username" id="mail" type="text" class="form-control" name="mail" placeholder="Введите email">
                    <div class="form-icon-wrapper">
                      <IconsMail class="form-icon" color="#B5BDDB" />
                    </div>
                    <div class="is-error-icon d-none">
                      <IconsMail class="form-icon" color="#fff" />
                    </div>
                  </div>
                  <div class="error-feedback-bordered d-none">
                    Неверная почта
                  </div>
                </div>
                <div class="form-group">
                  <label for="passwordInputForm" class="form-label">Пароль</label>
                  <div class="position-relative">
                    <input v-model="userStore.password" id="passwordInputForm" type="password" class="form-control" name="password" placeholder="Введите пароль">
                    <div class="form-icon-wrapper">
                      <IconsPassword class="form-icon" color="#B5BDDB" />
                    </div>
                    <div class="is-error-icon d-none">
                      <IconsPassword class="form-icon" color="#fff" />
                    </div>
                  </div>
                  <div class="error-feedback-bordered d-none">
                    Неверный пароль
                  </div>
                </div>
                <div class="d-grid mt-4">
                  <button :class="{'btn-disabled' : !userStore.username || !userStore.password}" type="submit" class="btn d-block">
                    Войти
                  </button>
                </div>
              </form>
              <ForgotPasswordLink class="forgotPasswordLink" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
  <ForgotPasswordModal />
</template>

<script setup>
import {useUserStore} from "~/stores/userStore";
const userStore = useUserStore()
const { status, data, signIn, signOut } = useSession()
</script>
<script>

export default {
  name: "index",
}
</script>

<style scoped>

</style>

认证逻辑文件(路径:server/api/auth/[...].ts)

// file: ~/server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth'
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";

export default NuxtAuthHandler({
    secret: 'your-secret-here',
    session: {
      strategy: 'jwt'
    },
    jwt: {
        // The maximum age of the NextAuth.js issued JWT in seconds.
        // Defaults to `session.maxAge`.
        maxAge: 60 * 60 * 24 * 30,
    },
    providers: [
        // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
        CredentialsProvider.default({
            // The name to display on the sign in form (e.g. 'Sign in with...')
            name: 'Credentials',
            // The credentials is used to generate a suitable form on the sign in page.
            // You can specify whatever fields you are expecting to be submitted.
            // e.g. domain, username, password, 2FA token, etc.
            // You can pass any HTML attribute to the <input> tag through the object.
            credentials: {
                username: { label: 'Username', type: 'text'},
                password: { label: 'Password', type: 'password'}
            },
            async authorize (credentials: any) {
                try {
                    // You need to provide your own logic here that takes the credentials
                    // submitted and returns either a object representing a user or value
                    // that is false/null if the credentials are invalid.
                    const usernameForm = credentials?.username
                    const passwordForm = credentials?.password
                    const config = {
                        url: 'https://api3.evrotrans.net/auth/login',
                        credentials: 'include',
                        method: 'post',
                        headers: {
                            "Content-type": "application/json; charset=UTF-8"
                        },
                        data: '{"login":"' + usernameForm +'","password":"' + passwordForm + '"}',
                    }
                    await axios.request(config).then((response) => {
                        const user = {username: usernameForm, password: passwordForm, token: response.data.token}
                        console.log(response.data.message)
                        if (response.data.error == 0) {
                            // Any object returned will be saved in `user` property of the JWT
                            console.log('авторизован')
                            return true
                        }
                        if (response.data.password == 'Incorrect login or password.') {
                            console.error('неправильный логин или пароль')
                            return null
                        }
                    })
                }
                catch (e) {
                    console.log(e)
                    return e
                }
            }
        })
    ],
})

问题排查与修复

核心问题1:authorize函数无有效返回值

你在authorize里使用了axios.request().then(...)的回调写法,回调内的return仅作用于内部函数,外层authorize实际返回undefined,会被Nuxt Auth判定为认证失败。

核心问题2:返回true不符合要求

Nuxt Auth要求authorize函数在认证成功时返回用户对象,而非true。返回true会被视为无效用户信息,导致会话无法创建。

核心问题3:错误处理逻辑错误

catch块返回错误对象e,会被判定为认证失败,正确做法是返回null或false表示认证失败。

修复后的认证逻辑代码

// file: ~/server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth'
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";

export default NuxtAuthHandler({
    secret: 'your-secret-here',
    session: {
      strategy: 'jwt'
    },
    jwt: {
        maxAge: 60 * 60 * 24 * 30,
    },
    providers: [
        CredentialsProvider.default({
            name: 'Credentials',
            credentials: {
                username: { label: 'Username', type: 'text'},
                password: { label: 'Password', type: 'password'}
            },
            async authorize (credentials: any) {
                try {
                    const usernameForm = credentials?.username
                    const passwordForm = credentials?.password
                    
                    // 改用await替代then回调,确保返回值传递到外层函数
                    const response = await axios.request({
                        url: 'https://api3.evrotrans.net/auth/login',
                        credentials: 'include',
                        method: 'post',
                        headers: {
                            "Content-type": "application/json; charset=UTF-8"
                        },
                        data: JSON.stringify({ login: usernameForm, password: passwordForm }), // 用JSON.stringify避免字符串拼接风险
                    })

                    if (response.data.error === 0) {
                        // 返回包含用户标识的对象,会被存入JWT
                        return { 
                            id: usernameForm,
                            username: usernameForm, 
                            token: response.data.token 
                        }
                    } 
                    
                    // 账号密码错误时返回null
                    if (response.data.password === 'Incorrect login or password.') {
                        console.error('неправильный логин или пароль')
                        return null
                    }

                    // 其他未知情况返回null
                    return null
                }
                catch (e) {
                    console.error('认证请求出错:', e)
                    // 认证失败必须返回null或false
                    return null
                }
            }
        })
    ],
})

额外优化点

  1. 用JSON.stringify替代手动拼接JSON字符串,避免语法错误和注入风险
  2. 错误日志改用console.error,更符合调试规范
  3. 移除冗余注释,保持代码简洁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 01:00:48