尝试为 React JS / Next JS 项目实现 NextAuth.js。当前运行 React JS 18.2.0、Next JS 13.0.6 和 NextAuth.js 4.18.0。
我正在使用服务器端渲染以及部署在 Vercel 上的 Next API 调用。为了确定会话,我在 getServerSideProps 中使用unstable_getServerSession,效果很好。现在我想使用 signIn 方法通过凭据提供程序登录用户。我的实现不断重定向到同一页面,并在 URL 中添加了一些参数:
网址如下: https://example.com/api/auth/signin?callbackUrl=%2F
显示:
{"boyd":"","query":{"callbackUrl":"/"},"cookies":{"__Host-next-auth.csrf-token":"token...","__Secure-next-auth.callback-url":"url..."}}
没有保存任何会话,我不知道发生了什么。我希望登录方法能够解决这个问题,但我假设服务器端渲染会阻止调用解析。
下面的代码描述了我的实现过程:
所用表格:
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<TextField
margin="normal"
required
fullWidth
id="phone"
label="Phone Number"
name="phone"
onChange={(e) => setPhone(e.target.value)}
autoComplete="phone"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
</Box>
表格手柄:
const [phone, setPhone] = useState("");
const [password, setPassword] = useState("");
const [submit, setSubmit] = useState(false);
{/* On submit use Next Auth signIn method */}
const handleSubmit = (e) => {
e.preventDefault();
setSubmit(true);
}
useEffect(() => {
if(submit){
signIn("credentials", {
callbackUrl: '/',
username: phone,
password: password,
})
}
}, [submit])
获取CSRF-token:
export async function getServerSideProps(context) {
return {
props: {
csrfToken: await getCsrfToken(context),
},
}
}
以及 [...nextauth].js 配置:
export const authOptions = {
// Authentication providers
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
id: 'credentials',
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: "Phone", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req){
const user_data = await checkCredentials(credentials.username, credentials.password);
if(user_data.success){
return user_data.user;
} else{
return null;
}
}
}),
// ...add more providers here
],
adapter : PrismaAdapter(prisma),
session: {
// Choose how you want to save the user session.
// The default is `"jwt"`, an encrypted JWT (JWE) stored in the session cookie.
// If you use an `adapter` however, we default it to `"database"` instead.
// You can still force a JWT session by explicitly defining `"jwt"`.
// When using `"database"`, the session cookie will only contain a `sessionToken` value,
// which is used to look up the session in the database.
strategy: "jwt",
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
// The session token is usually either a random UUID or string, however if you
// need a more customized session token string, you can define your own generate function.
generateSessionToken: () => {
return uid(32);
}
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return Promise.resolve(true);
},
async redirect({ url, baseUrl }) {
return Promise.resolve(url);
},
async jwt({ token, user, account, profile, isNewUser }) {
return Promise.resolve(token);
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.user.image = '';
return Promise.resolve(session);
},
async credentials({ user, account, profile }){
return Promise.resolve(true);
}
},
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: '/login'
},
debug: false
}
如果您有任何关于如何解决此问题的提示,请告诉我:)
我设法找出问题所在。我的 api 文件夹中有一个 index.js 文件,给出了标准的 ok 回复。删除路由后,Vercel 在访问 /api/auth/* 路由时显示 404 错误。结果我的 vercel.json 配置阻止了这些调用。删除 vercel.json 中的以下行后,一切正常:
"rewrites": [{ "source": "/api/(.*)", "destination": "/api" }]
对于那些仍在寻找从
signIn
强制使用 getServerSideProps
的人,您可以将重定向返回到登录端点 - /api/auth/signin/<provider>
。
例如。我想在浏览器中没有会话的情况下强制用户登录。
export async function getServerSideProps(ctx) {
const session = await getSession(ctx);
if (!session) {
return {
redirect: {
destination: `/api/auth/signin/google`,
permanent: false,
},
};
} else {
return {
props: {},
}
}
}
export const Home = () => {
return (
<div>Welcome Home!</div>
)
}
我找到了一种从React服务器组件中解决它的方法,代码可能也可以工作并且在涉及
getServerSideProps
时类似。
import { AuthHandler } from "node_modules/next-auth/core";
import { type PageProps } from ".next/types/app/layout";
import { getServerAuthSession } from "@/server/auth";
import { cookies, headers } from "next/headers";
import { authOptions } from "@/server/auth";
import { type AuthAction } from "next-auth";
import { redirect } from "next/navigation";
export default async function Page({ searchParams }: PageProps) {
const callbackUrl = (searchParams?.callbackUrl as string) ?? "/";
const session = await getServerAuthSession();
if (session) {
redirect(callbackUrl);
}
const data = {
req: {
body: {
csrfToken: cookies().get("next-auth.csrf-token")?.value.split("|")[0],
callbackUrl,
json: "true",
},
method: "POST",
cookies: Object.fromEntries(
cookies()
.getAll()
.map((c) => [c.name, c.value]),
),
headers: Object.fromEntries(headers()),
action: "signin" as AuthAction,
providerId: "provider-id", // Replace to use the id of your own provider
error: "provider-id", // Replace to use the id of your own provider
},
options: authOptions,
};
const res = await AuthHandler(data);
redirect(res.redirect ?? "/");