本章的所有代码都可以在 GitHub 上找到,分支为 credentialssignin
。
https://github.com/peterlidee/NNAS/tree/credentialssignin
我们还需要处理以下几个问题:
- 表单输入验证
- 处理
signIn
返回的错误 - 处理成功登录后的操作
- 处理加载状态
表单验证
我们将使用客户端的 Zod 进行输入验证。Zod 处理以 TypeScript 为优先的模式进行架构验证,并支持静态类型推断。这意味着它不仅会验证你的字段,还会为验证后的字段设置类型。
虽然我对 Zod 还不太熟悉,但它似乎与服务器操作和 useFormState
钩子配合得很好。我们稍后会在这个上下文中使用它,但现在我们会在客户端使用。首先安装 Zod:
npm install zod
我们需要验证用户名/电子邮件和密码输入字段。这些都在我们之前设置的 useState
钩子中。我们首先创建一个 formSchema
:
const formSchema = z.object({
identifier: z.string().min(2).max(30),
password: z
.string()
.min(6, { message: '密码长度至少为 6 个字符。' })
.max(30),
});
这很容易理解。我们希望两个字段都是字符串,并且有最小和最大长度。当密码太短时,我们提供一个自定义错误信息。注意,关于密码验证的文章和观点很多,请根据自己的需要进行处理。
在 handleSubmit
函数中,在调用 signIn
之前,我们调用这个 formSchema
。它将检查我们的输入字段值是否符合我们在 formSchema
中设置的条件。通过使用 validatedFields.success
属性,我们可以处理接下来需要发生的事情。
在出错时,Zod 会为我们生成错误消息,我们将其保存在状态中。成功时,由于我们的输入是有效的,我们可以简单地调用 signIn
函数。
type FormErrorsT = {
identifier?: undefined | string[],
password?: undefined | string[],
};
const [errors, setErrors] = useState < FormErrorsT > {};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const validatedFields = formSchema.safeParse(data);
if (!validatedFields.success) {
setErrors(validatedFields.error.formErrors.fieldErrors);
} else {
// 没有 Zod 错误
// 调用 signIn
}
};
最后,更新我们的表单 JSX 以显示错误消息。对于每个输入字段,我们可以这样处理:
{
errors?.identifier ? (
<div className='text-red-700' aria-live='polite'>
{errors.identifier[0]}
</div>
) : null;
}
以及在表单下方显示一般错误:
{
errors.password || errors.identifier ? (
<div className='text-red-700' aria-live='polite'>
出了点问题。请检查您的数据。
</div>
) : null;
}
处理 NextAuth signIn
函数返回的错误
在上一章中,我们通过在 authorize
函数中抛出错误来处理 Strapi 错误。在前端,我们在 signIn
函数的选项对象中添加了 redirect: false
属性。这使得 signIn
返回一个带有 error
属性的对象:
{
error?: string;
status: number;
ok: boolean;
url?: string;
}
现在我们将使用这个对象向用户提供反馈。我们检查是否有错误,然后将该错误放入我们已经用于 Zod 错误的错误状态中。我们扩展我们的错误类型,添加 strapiError
属性:
type FormErrorsT = {
identifier?: undefined | string[];
password?: undefined | string[];
strapiError?: string;
};
监听 signInResponse
中的错误并将其放入错误状态中:
if (signInResponse && !signInResponse?.ok) {
setErrors({
strapiError: signInResponse.error
? signInResponse.error
: '出了点问题。',
});
} else {
// 处理成功
}
在表单下方显示 strapiError
:
{
errors.strapiError ? (
<div className='text-red-700' aria-live='polite'>
出了点问题:{errors.strapiError}
</div>
) : null;
}
我们现在正在表单组件中显示我们在 authorize
中抛出的自定义错误!
处理成功登录
我们还没有实际处理通过凭据成功登录的过程。当我们没有错误时,表明我们已经成功登录。那么接下来要做什么呢?我们之前设置了 GoogleProvider
,以在成功登录时重定向到上一页。
这是一个我不会在生产环境中采用的解决方案。例如,如果上一页是注册页面,那么在登录后将他们返回到注册页面将是糟糕的用户体验。但我将把这个决定留给你,并坚持简单的重定向。
如前所述,我们只需从 callbackUrl
搜索参数中获取我们的 URL:
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') || '/';
const router = useRouter();
然后我们只需推送新路由:
router.push(callbackUrl);
这里注意:signIn
函数选项对象有一个 callbackUrl
属性。如果你喜欢,可以使用它来重定向到一个固定的页面。
让我们测试一下我们刚刚编写的代码。运行我们的应用并尝试使用正确的凭据登录。一切正常,我们被重定向了
更新 getServerSession
客户端和服务器会话
事情出了点问题!显然的问题是我们的 <LoggedInClient />
组件(使用 useSession
钩子)说我们已登录,但我们的服务器组件 <LoggedInServer />
(使用 getServerSession
)却说我们没有登录。我们的 <NavbarUser />
组件(使用 getServerSession
)也失败了。它应该显示用户名并跟随 <SignOutButton />
,但它没有显示用户名并显示了登录链接。
因此,似乎 getServerSession
存在问题。它似乎没有更新。是的,我们确实已登录,因此 useSession
是正确的。转向 NextAuth 文档没有提供答案。没有 NextAuth 方法可以手动触发 getServerSession
刷新。
我花了相当多的时间试图弄清楚这个问题。我们做的一个改变是添加了 redirect: false
选项。我们暂时删除这个 redirect
选项并尝试登录时,我们注意到了一些事情。页面会完全刷新!这意味着 Next 和 NextAuth 获取了新的更新数据。
当 redirect: false
选项重新启用时,当凭据成功登录时,我们执行 router.push
。这不会触发页面刷新,正如我们在单页应用程序中所预期的那样。但这也意味着我们的服务器组件(如 <LoggedInServer />
和 <NavbarUser />
)没有刷新并显示了过时的状态。
这就是导致我们问题的原因。我们如何解决这个问题?我查看了很多资料,谷歌搜索并阅读了许多内容,直到有人提到 router.refresh
。根据 Next 文档:
router.refresh(): 刷新当前路由。向服务器发出新的请求,重新获取数据请求,并重新渲染服务器组件。客户端将合并更新的 React 服务器组件负载,而不会丢失未受影响的客户端 React(例如 `useState`)或浏览器状态(例如滚动位置)。
这解决了问题。我们添加这一行并再次测试:
// 处理成功
router.push(callbackUrl);
router.refresh();
设置加载状态
让我们处理在较慢的连接上快速点击按钮时禁用按钮的情况。
创建一个加载状态并将其初始化为 false
。当我们提交时,将加载状态设置为 true
。在 Zod 错误或 Strapi 错误时,将其设置为 false
。更新按钮以包含 disabled
属性和一些禁用样式:
<button
type='submit'
className='bg-blue-400 px-4 py-2 rounded-md disabled:bg-sky-200 disabled:text-gray-400 disabled:cursor-wait'
disabled={loading}
aria-disabled={loading}
>
登录
</button>
完成!请查看 GitHub 上完成的 <SignIn />
组件。
结论
我们刚刚设置了一个凭据登录流程,并且花了一些时间(3 章)!以下是我们所做的事情的概述:
- 将 CredentialsProvider 添加到 NextAuth 提供程序。
- 设置一个带有受控输入的表单。
- 在客户端使用凭据调用
signIn
。 - 解释并写出 `authorize
` 函数/回调。
- 调用 Strapi 登录端点。
- 从
authorize
中返回响应。 - 更新
jwt
回调。 - 修复回调类型。
- 探索 NextAuth 默认的
authorize
函数错误处理。 - 在
authorize
中处理 Strapi 错误。 - 使用
redirect: false
更新signIn
。 - 使用 Zod 进行表单验证。
- 处理
signIn
的返回值。 - 在成功登录时重定向用户。
- 修复
getServerSession
未重新加载的问题。 - 添加加载状态。
认证流程是复杂的。到现在为止,你应该对 NextAuth 有了很好的理解,并知道如何在自己的项目中使用它。希望我能够清楚地解释一切,并帮助你节省一些时间。在下一章中,我们将为 CredentialsProvider 实现一个注册流程。
首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。