NEXTAUTH_URL 不起作用时如何定义自定义基本路径?

How to define a custom base path when NEXTAUTH_URL doesn't work?

上下文

我正在使用 NextJS v12.0.7React v17.0.2NextAuth v4.1.0Typescript v4.3.5

我想基于 NextAuth documentation 创建一个简单的身份验证系统,并在登录后重定向到主页,所以我在 pages/api/auth 文件夹中创建了一个名为 [...nextauth].ts 的文件包含以下代码部分:

export default NextAuth({
  providers: [
    CredentialsProvider({
      id: "credentials",
      name: "Login",
      credentials: {
        username: { type: "email" },
        password: { type: "password" },
      },
      async authorize(credentials) {
          // [...]
      },
    }),
  ],

  // [...]

  callbacks: {
    // [...]
    redirect({ url, baseUrl }) {
      console.log("redirect - url ", url, "baseUrl ", baseUrl); // This is what I was checking
      if(url.startsWith(baseUrl)) {
        return url
      } else if(url.startsWith("/")) {
        return new URL(url, baseUrl).toString();
      }
      return baseUrl;
    }
  },
})

对于我的 NextJS 项目,我使用了 next.config.js 文件中定义的基本路径 /espace-personnel,所以我在 .env.local 文件中定义了 NEXTAUTH_URL NEXTAUTH_URL 37=]:

NEXTAUTH_URL=http://localhost/espace-personnel/api/auth

我的问题

我在重定向回调中检查 urlbaseUrl 变量的地方,我可以看到我的 NEXTAUTH_URL 变量定义不正确,或者我误解了一些东西。

这是 console.log("redirect - url ", url, "baseUrl ", baseUrl); 的结果:

redirect - url  http://localhost:3000 baseUrl  http://localhost

我已经做过研究,但在 Google 或 Whosebug 上找不到像我这样的案例,我是 NextAuth 的新手,所以我可能误解了一些东西。有人知道我做错了什么吗?

编辑 (15/01/2022)

再三考虑后,我决定直接使用 process.env.NEXTAUTH_URL 检查我的 env 变量,所以我在之前的 console.log() :

之后添加了该行
  console.log("NEXTAUTH_URL", process.env.NEXTAUTH_URL)

结果如下:

NEXTAUTH_URL http://localhost/espace-personnel/api/auth

所以,事实上,这不是定义 NEXTAUTH_URL 的问题,我想我对 NextAuth 的工作方式有一些误解,我会继续尝试找到解决我的问题的方法并在那里分享我在找东西。

编辑 (16/01/2022)

经过一些研究,我发现我们可以使用 signIn 方法传递 callbackUrl,所以我在 [...nextauth].ts 中定义了我的页面:

  pages: {
    signIn: process.env.NEXT_PUBLIC_BASE_URL + '/connexion',
    signOut:  process.env.NEXT_PUBLIC_BASE_URL + '/deconnexion',
    error:  process.env.NEXT_PUBLIC_BASE_URL + '/connexion/erreur', // Error code passed in query string as ?error=
  },

这是我的登录表单代码:

import React, { useState } from "react";
import Alert from "../../ui/Alert/Alert";
import Button from "../../ui/Button/Button";
import Card from "../../ui/Card/Card";
import Divider from "../../ui/Divider/Divider";
import { getCsrfToken, signIn } from "next-auth/react";
import Input from "../../ui/Input/Input";
import styles from "./LoginForm.module.scss";
import Router from 'next/router';

type TAlertTitle = string;
type TAlertMessage = string;
type TAlertType = "info" | "warning" | "success" | "error";

export default function LoginForm({ csrfToken }: { csrfToken: string }) {
  const [alertTitle, setAlertTitle] = useState<TAlertTitle>("Information");
  const [alertMessage, setAlertMessage] = useState<TAlertMessage>("Juste un exemple");
  const [alertType, setAlertType] = useState<TAlertType>("info");

  async function onSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
    const target = event.target as typeof event.target & {
      username: { value: string },
      password: { value: string },
    };

    // Login attempt
    const result = signIn('credentials', {
      callbackUrl: process.env.NEXT_PUBLIC_BASE_URL,
      username: target.username.value,
      password: target.password.value
    });

    console.log(result);

    event.preventDefault();
  }

  return (
    <Card className={styles.card}>
      <p className={'textSize40 fontWeightExtraBold textAlignCenter'}>
        Connexion à votre espace personnel
      </p>
      <Divider className={styles.divider} />
      <form onSubmit={onSubmit}>
        <Alert title={alertTitle} message={alertMessage} type={alertType}></Alert>
        <input name="csrfToken" type="hidden" defaultValue={csrfToken} />
        <Input id="username" name="username" className="width100" type="email" autoComplete="email" placeholder="Adresse mail" leftIconLib="line-awesome" leftIcon="envelope" required />
        <Input id="password" name="password" className="width100" type="password" autoComplete="current-password" placeholder="Mot de passe" leftIconLib="line-awesome" leftIcon="key" required />
        <Button className="width100" theme="accent" type="submit">Se connecter</Button>
        <Button className="width100" type="submit">Identifiants oubliés</Button>
      </form>
    </Card>
  );

}

新问题是,当我尝试登录时,我被重定向到 http://localhost/api/auth/error 并且我收到了该错误:

404 | This page could not be found.

我是不是做错了什么?

经过几个小时的研究,我终于找到了 very recent Github discussion concerning the same problem

在 NextAuth v4 中使用自定义基本路径的解决方案:

编辑您的 _app.tsx 并使用 <SessionProvider> 上的道具 basePath 定义您的基本路径,这是我使用自定义路径 /espace-personnel 的最终代码:

function App({ Component, pageProps: { session, ...pageProps} }: AppProps) {
  return (
    <SessionProvider session={session} basePath="/espace-personnel/api/auth">
      <Component {...pageProps} />
    </SessionProvider>
  )
}
export default App

如果您对多个区域或自定义基本路径使用 next.js 身份验证,则需要进行一些更改以使 NextAuth.js 意识到这一点

1- 更新其余 api 路径基础,.env 文件

NEXTAUTH_URL=https://example.com

成为

NEXTAUTH_URL=https://example.com/{zone name or basePath}/api/auth

2-更新客户端api路径基础,_app.tsx

<SessionProvider session={pageProps.session}>

成为

<SessionProvider session={pageProps.session} basePath="/{zone name or basePath}/api/auth">