This repository was archived by the owner on Mar 5, 2025. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 29
[Production Only] SignIn method calls api/auth/callback/credentials with error 403: CSRF Token Mismatch #166
Copy link
Copy link
Open
Description
Environment
- Operating System: Linux
- Node Version: v18.19.0
- Nuxt Version: 3.10.0
- CLI Version: 3.10.0
- Nitro Version: 2.8.1
- Package Manager: yarn@1.22.21
- Builder: -
- User Config: app, devtools, typescript, $production, $development, runtimeConfig, nitro, vite, plugins, image, build, modules, components, alias, css
- Runtime Modules: @nuxt/image@1.3.0, (), @hebilicious/authjs-nuxt@0.3.5
- Build Modules: -
Reproduction
- Url: https://admin.hexasync.com
- User & pwd: any user & password.
- Click Submit
Actual: the network console will returns 403: Forbidden because of CSRF Token Mismatch
I take a look at the networking behind the scene and saw that it always calls api/auth/callback/credentials? with undefined csrfToken.
Question: How can I set the csrfToken and how does the nuxt server api verify it? I don't see any setup for csrfToken in nuxt tutorial, neither this site's tutorial.
# request
Request URL:
https://admin.hexasync.com/api/auth/callback/credentials?
Request Method:
POST
Status Code:
403 Forbidden
Payload:
- redirect: false
- username: team@beehexa.com
- password: abc123456
- csrfToken: undefined // I don't know why this is undefined and how to fille its value.
- callbackUrl: https://admin.hexasync.com/login
# response
{
"url": "/api/auth/callback/credentials?",
"statusCode": 403,
"statusMessage": "CSRF Token Mismatch",
"message": "CSRF Token Mismatch",
"stack": ""
}Describe the bug
- There is no module related to csrf installed
- There is no security module installed
- The signIn() method always calls
api/auth/callback/credentialsand there is no way to set the csrfToken. Thus, the csrfToken always null/undefined. - The nuxt server will not accept the request.
Below is the configurations
# nuxt.config.ts
import * as antd from 'ant-design-vue'
import { addComponent } from '@nuxt/kit'
import { resolve } from "node:path"
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
app: {
// baseURL: '/profiles',
head: {
title: 'HexaSync Integration Platform',
meta: [
{ name: 'description', content: 'HexaSync Admin' }
],
link: [
// { rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
]
}
},
devtools: { enabled: true },
typescript: {
shim: false,
typeCheck: true
},
$production: {
routeRules: {
'/**': { isr: true }
}
},
$development: {
//
},
runtimeConfig: {
// The private keys which are only available server-side
// apiSecret: '123',
// Keys within public are also exposed client-side
authJs: {
secret: process.env.NUXT_NEXTAUTH_SECRET, // You can generate one with `openssl rand -base64 32`
guestRedirectTo: "/login",
authenticatedRedirectTo: "/"
},
hexasync: {
ssoSecret: process.env.SSO_SERVICE_SECRET,
ssoUrl: process.env.SSO_SERVICE_URL,
profileUrl: process.env.PROFILE_SERVICE_URL
},
// github: {
// },
public: {
// apiBase: '/api'
authJs: {
baseUrl: process.env.NUXT_NEXTAUTH_URL,
verifyClientOnEveryRequest: true,
}
}
},
nitro: {
routeRules: {
"/": { ssr: true, prerender: false },
"/sso-proxy/**": { proxy: `${process.env.SSO_SERVICE_URL}/**` },
}
},
vite: {
vue: {
customElement: true
},
vueJsx: {
mergeProps: true
}
},
plugins: [
],
image: {
inject: true,
quality: 80
},
build: {
transpile: ['lodash']
},
modules: [
// '@nuxtjs/vuetify',
// 'nuxt-vite',
// '@nuxt/vite-builder',
'@nuxt/image',
async function (options, nuxt) {
for (const key in antd) {
if (['version', 'install'].includes(key)) continue
await addComponent({
filePath: 'ant-design-vue',
name: `A${key}`,
export: key
})
}
},
'@hebilicious/authjs-nuxt'
],
components: {
global: true,
dirs: ['~/components']
},
alias: {
cookie: resolve(__dirname, "node_modules/cookie")
},
css: [
'~/assets/scss/main.scss'
]
})# packages.json
{
"name": "nuxt-app",
"private": true,
"type": "module",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"scripts": {
"build": "nuxt build --standalone",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/vite-builder": "^3.10.0",
"@types/jsonwebtoken": "^9.0.5",
"@types/lodash": "^4.14.202",
"@types/luxon": "^3.4.2",
"@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/babel-plugin-jsx": "^1.2.1",
"eslint": "^8.56.0",
"node-gyp": "^10.0.1",
"nuxt": "^3.10.0",
"nuxt-security": "^1.1.1",
"sass": "^1.70.0",
"typescript": "^5.3.3",
"vue": "^3.4.15",
"vue-router": "^4.2.5",
"vue-tsc": "^1.8.27"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@auth/core": "^0.17.0",
"@hebilicious/authjs-nuxt": "^0.3.5",
"@nuxt/image": "^1.3.0",
"ant-design-vue": "^4.1.2",
"chart.js": "^4.4.1",
"chartjs-adapter-luxon": "^1.3.1",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"luxon": "^3.4.4",
"node-addon-api": "^7.1.0",
"vue-chartjs": "^5.3.0"
}
}
# /server/api/auth/[...].ts
import CredentialsProvider from "@auth/core/providers/credentials"
import type { AuthConfig } from "@auth/core/types"
import { NuxtAuthHandler } from "#auth"
import { AccountService } from "~/services/accountService"
import { verifyToken } from "~/utils/jwt"
// The #auth virtual import comes from this module. You can use it on the client
// and server side, however not every export is universal. For example do not
// use sign-in and sign-out on the server side.
const runtimeConfig = useRuntimeConfig()
// Refer to Auth.js docs for more details
export const authOptions: AuthConfig = {
// secret: runtimeConfig.authJs.secret,
secret: process.env.NUXT_NEXTAUTH_SECRET,
session: {
strategy: 'jwt'
},
providers: [
CredentialsProvider({
id: 'credentials',
type: 'credentials',
name: 'credentials',
credentials: {
username: { label: "Username", type: "text", placeholder: "admin@beehexa.com" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const accountService = new AccountService(runtimeConfig)
const jwt = await accountService.login(credentials.username as string, credentials.password as string)
const user = await accountService.me(jwt.accessToken)
if (user?.email?.indexOf('beehexa.com') || user?.email?.indexOf('hexasync.com')) {
return {...user, jwt: {...jwt}}
}
return null as any
}
})
],
callbacks: {
async jwt({ token, user }) {
if (!token) {
return {}
}
// it is token, but it is not. It's a user info with jwt token
let result = {...token} as any
if (user) {
result = {...result, ...user}
}
let {jwt} = result
const accessToken = await verifyToken(jwt.accessToken)
if (accessToken.isValid) {
return {...result, claims: {...accessToken.decoded}}
}
const refreshToken = await verifyToken(jwt.refreshToken)
if (!refreshToken.isValid) {
return {}
}
const accountService = new AccountService(runtimeConfig)
jwt = await accountService.refreshToken(jwt.refreshToken)
const newAccessToken = await verifyToken(jwt.accessToken)
return {
...result,
...user,
jwt: {...jwt},
claims: {...newAccessToken.decoded}
}
},
async session({ session, token }) {
if (!token) {
return {} as any
}
// it is token, but it is not. It's a user info with jwt token
return {
...session,
user: {
...token
},
token: {
...token
}
}
}
}
}
export default NuxtAuthHandler(authOptions, runtimeConfig)After click Submit, the Auth Module calls /api/auth/callback/credentials and received:
Request URL:
https://admin.hexasync.com/api/auth/callback/credentials?
Request Method:
POST
Status Code:
403 Forbidden
Payload:
- redirect: false
- username: team@beehexa.com
- password: abc123456
- csrfToken: undefined
- callbackUrl: https://admin.hexasync.com/login
Response: {
"url": "/api/auth/callback/credentials?",
"statusCode": 403,
"statusMessage": "CSRF Token Mismatch",
"message": "CSRF Token Mismatch",
"stack": ""
}
Additional context
No response
Logs
No response
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels