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

[Production Only] SignIn method calls api/auth/callback/credentials with error 403: CSRF Token Mismatch #166

Open
beeGiaLe opened this issue Feb 8, 2024 · 7 comments

Comments

@beeGiaLe
Copy link

beeGiaLe commented Feb 8, 2024

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: [email protected]
  • Builder: -
  • User Config: app, devtools, typescript, $production, $development, runtimeConfig, nitro, vite, plugins, image, build, modules, components, alias, css
  • Runtime Modules: @nuxt/[email protected], (), @hebilicious/[email protected]
  • Build Modules: -

Reproduction

  1. Url: https://admin.hexasync.com
  2. User & pwd: any user & password.
  3. 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: [email protected]
- 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

  1. There is no module related to csrf installed
  2. There is no security module installed
  3. The signIn() method always calls api/auth/callback/credentials and there is no way to set the csrfToken. Thus, the csrfToken always null/undefined.
  4. 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: "[email protected]" },
        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: [email protected]
- 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

@beeGiaLe
Copy link
Author

beeGiaLe commented Feb 8, 2024

Screenshot_20240208_175444_Edge.jpg

Screenshot_20240208_175517_Edge.jpg

@Panca6
Copy link

Panca6 commented Mar 1, 2024

same problem is anyone able to figure this out somehow?

@espensgr
Copy link

espensgr commented Mar 4, 2024

Auth.js has a skipCSRFcheck parameter in the config, but i cannot get it to work.
It does not accept a Boolean it seems, it wants an Symbol 🤷‍♂️

@Wazbat
Copy link

Wazbat commented May 20, 2024

I think I'm getting the same problem here? A CSRF protected error

@ghyath5
Copy link

ghyath5 commented Jun 11, 2024

Is this package dead ??

I'm facing the same error in production.
We can't event set skipCSRFcheck to true.

@Wazbat
Copy link

Wazbat commented Jun 11, 2024

@ghyath5 I was able to resolve some of my problems by pinning @auth/core to a specific version in my package.json

{
    //...
    "@auth/core": "0.17.0",
    "@hebilicious/authjs-nuxt": "^0.3.5",
    //...
}

@JoshCunningHum
Copy link

JoshCunningHum commented Sep 9, 2024

the skipCSRFcheck is a unique symbol that you have to import:

import { skipCSRFCheck } from "@auth/core";

then used as:

export const authOptions: AuthConfig = {
    // ...,
    skipCSRFCheck,
};

However despite this, I do still have the same error in prod, and my @auth/core is in version 0.17.0

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

No branches or pull requests

6 participants