这一章比较棘手,因为我们需要引入一些新的概念。此外,NextAuth 提供的错误反馈非常有限,这让人有些困惑。我们将从一些稍微偏题的点开始,然后回到错误处理上来。
最终的代码可以在 GitHub 上找到(分支:callbacksForGoogleProvider)。
https://github.com/peterlidee/NNAS/tree/callbacksForGoogleProvider
Next.js 错误边界
我们从在 Next.js 前端添加一些错误处理开始,通过在项目的根目录中添加 error.tsx
文件。我们使用文档中的基本示例:
// frontend/src/app/error.tsx
'use client'; // 错误组件必须是客户端组件
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string },
reset: () => void,
}) {
useEffect(() => {
// 将错误记录到错误报告服务
console.error(error);
}, [error]);
return (
<div>
<h2>出了些问题!</h2>
<p>根错误:{error.message}</p>
<button
onClick={
// 尝试通过重新渲染该部分来恢复
() => reset()
}
>
再试一次
</button>
</div>
);
}
当出现未捕获的错误时,这个组件将会捕获它。这与主题有点偏离,因为 NextAuth 的 GoogleProvider 不会产生未捕获的错误。但由于这一章是关于错误的,我们在这里添加了这个组件。
NextAuth 的 signIn
回调
我们在之前的章节中简要提到过这个回调:该回调允许你控制用户是否被允许登录。
async signIn({ user, account, profile, email, credentials }) {
return true;
}
我们为什么需要这个?假设用户创建了一个 Google 账户,但没有验证该账户。我们不希望用户使用这个未验证的账户连接到我们的应用程序。signIn
回调有许多参数,我们之前已经介绍过。profile
是其中之一,它包含 Google 返回的原始用户数据。profile
有一个属性 email_verified
。我们可以这样做:
async signIn({ user, account, profile, email, credentials }) {
if (!profile.email_verified) return false;
return true;
}
返回 false
会阻止该用户的整个认证流程。此外,用户将被重定向到默认或自定义的 NextAuth 错误页面。
在 NextAuth 中创建自定义错误页面
除了有默认的登录页面(我们之前替换过的丑陋的那个),NextAuth 还有一个默认的错误页面。我们可以并且将会替换这个默认页面为自定义错误页面。注意,这与我们之前创建的 error.tsx
不同。
NextAuth 在何时使用此页面?文档指出,当发生以下情况时,用户将被重定向到此错误页面:
authOptions
中的配置错误。AccessDenied
错误:当你通过signIn
回调或redirect
回调限制了访问时。- 验证错误:与电子邮件提供程序相关。
- 默认错误:其他一些情况。
我们刚刚看到了 signIn
回调。当它返回 false
时,我们会被重定向到错误页面。所以,让我们试一下,在 signIn
回调中返回 false
:
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
async signIn({ user, account, profile, email, credentials }) {
return false;
}
}
我们只是想看到错误页面,所以我们暂时对所有内容返回 false
并尝试登录。果然如预期,我们被重定向到默认的错误页面:
NextAuth 默认错误页面
它和我们之前看到的默认登录页面风格一样“经典”。但我们是不是缺少了错误信息?并没有,NextAuth 将其放在了 URL 中的 error
查询参数中:?error=AccessDenied
。
http://localhost:3000/api/auth/error?error=AccessDenied
那么剩下的消息在哪里?这就是全部了。我们稍后会回到这一点。首先我们完成这个错误页面部分。
自定义错误页面
和登录页面一样,我们也可以创建一个自定义错误页面。创建一个页面:
// frontend/src/app/(auth)/authError/page.tsx
type Props = {
searchParams: {
error?: string,
},
};
export default function AuthErrorPage({ searchParams }: Props) {
return (
<div className='bg-zinc-100 rounded-sm p-4 mb-4'>
AuthError: {searchParams.error}
</div>
);
}
然后在 authOptions.page
中告诉 NextAuth 使用此页面:
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
pages: {
signIn: '/signin',
error: '/authError',
},
我们测试一下,所有都正常工作。NextAuth 现在使用我们的自定义错误页面。如前所述,我们稍后会处理错误消息。
总结
我们发现 NextAuth 有一个错误页面。当发生以下情况时,我们会被重定向到此页面:
authOptions
中存在配置错误 (/authError?error=Configuration
)。- 我们使用了
signIn
或redirect
回调 (/authError?error=AccessDenied
)。 - 存在验证错误 (
/authError?error=Verification
)。 - 还有其他错误 (
/authError?error=Default
)。
我们已经用一个自定义页面替换了默认的错误页面。这样我们就处理了在 NextAuth 中可能遇到的一些错误。在继续之前,我们还需要提到一些其他要点。
在 signIn
回调中,当我们不希望用户能够通过认证时,我们返回 false
。但还有另一种选择,我们也可以返回一个相对路径。这将覆盖错误页面,并将用户重定向到指定的路径。例如,你可以将用户重定向到专门为此目的创建的另一个路由。
最后,我们实际实现前面提到的示例,即未验证的 Google 账户。我们更新 signIn
回调:
async signIn({ user, account, profile }) {
// console.log('singIn callback', { account, profile, user });
if (
account &&
account.provider === 'google' &&
profile &&
'email_verified' in profile
) {
if (!profile.email_verified) return false;
}
return true;
}
(我们不得不添加一些类型检查,因为 TypeScript 对我们发出了一些警告。)
其他错误
我们发出的每个请求都有可能失败或返回错误。我们应该考虑到这一点。在我们到目前为止的登录流程中,我们使用了 4 个请求:
- 发起登录请求。
- 发起登出请求。
- 调用 Google。
- 调用 Strapi。
调用 signIn
和 signOut
NextAuth 内部使用了一个 REST API 端点来处理其所有的流程。这意味着对该端点的请求可能会出错。
我试着制造一个错误,例如 signIn('foobar', {...})
,但什么都没有发生。没有控制台或终端中的错误,也没有 URL 中的错误参数。这让我得出结论,你可以安全地调用 signIn
和 signOut
而不必试图捕捉错误。
Google OAuth 请求
当使用 GoogleProvider 登录时,NextAuth 会在某个时间点向 Google OAuth 发起请求。由于这是一个请求,所以它可能出错。这些 OAuth 错误可能包括:
- 未验证的应用程序。
- 无效的令牌。
- 不正确的回调。
这些都是可能的错误。那么我们该如何处理这些错误呢?首先,让我们制造一个错误。在 authOptions
中,我们将 clientSecret
替换为一个随机字符串,看看会发生什么:
// clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
clientSecret: 'foobar',
我们运行应用程序并进行登录。结果表明,表面上似乎没有发生什么变化。我们没有登录,没有错误弹出,应用程序没有崩溃,也没有被重定向。但有一些事情发生了。首先,我们的 URL 现在看起来像这样:
http://localhost:3000/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F&error=OAuthCallback
// 解码后
http://localhost:3000/signin?callbackUrl=http://localhost:3000/&error=OAuthCallback
所以,基础 URL 是 http://localhost:3000/signin
,我们有两个查询参数:callbackUrl
和 error
。error
的值是 OAuthCallback
。其次,在我们的前端终端中,出现了完整的错误信息:
[next-auth][error][OAUTH_CALLBACK_ERROR]
https://next-auth.js.org/errors#oauth_callback_error invalid_client (Unauthorized) {
error
: OPError: invalid_client (Unauthorized)
at processResponse ...
...
providerId: 'google',
message: 'invalid_client (Unauthorized)'
}
所以,NextAuth 记录了错误为 OAUTH_CALLBACK_ERROR
,而原始的 Google OAuth 错误可能是 invalid_client (Unauthorized)
。这发生在终端中。在我们的客户端(浏览器)中,NextAuth 给了我们一个错误参数:error=OAuthCallback
。(浏览器控制台中没有错误日志!)
NextAuth 错误和错误代码
NextAuth 区分了错误和错误代码。OAUTH_CALLBACK_ERROR
是一个 NextAuth 错误,并在终端中记录下来。?error=OAuthCallback
是一个 NextAuth 错误代码,它被作为查询参数添加到回调 URL 中。
认证流程中的任何问题都会被 NextAuth 捕获,并分类为一个 NextAuth 错误。你可以在文档中找到这些错误的完整列表。
https://next-auth.js.org/errors
关于错误代码,NextAuth 这样说:
为了提高安全性,我们故意限制了返回的错误代码。
NextAuth 将错误作为查询参数传递到两个页面:
- 默认或自定义登录页面。
- 默认或自定义错误页面。
上述示例是一个作为查询参数发送到我们的自定义登录页面的错误。我们创建了一个自定义登录页面(/signin
),并接收到了一个错误:/signin?error=OAuthCallback
。之前,我们已经讨论过传递给 NextAuth 错误页面的错误(例如 Configuration
、AccessDenied
)。
最小的错误消息
因此,NextAuth 错误(终端)非常详细,但我们无法将其用于用户反馈(这是有意为之的)。那么它们的作用是什么?我不确定。也许是用于开发过程中解决问题。在生产环境中,可以通过检查日志来解决问题。
然而,我们仍然需要一些用户反馈。NextAuth 仅提供了一个单词的错误代码,例如 OAuthCallback
、callback
或 AccessDenied
。因此,作为开发者,你需要为每个代码想出一些巧妙的错误消息。例如:
const errorMap = {
'OAuthCallback': '这里是一个非常巧妙且用户体验友好的消息。',
'callback': '这是另一个示例消息。',
'AccessDenied': '还有更多吗?',
};
// 在前端调用它们
{errorMap[searchParams.error]}
所以,祝你好运。
处理 NextAuth 中的登录错误
我们仍然需要实际在登录页面上显示错误。创建一个新的客户端组件:
// frontend/src/components/auth/signin/GoogleSignInError.tsx
'use client';
import { useSearchParams } from 'next/navigation';
export default function GoogleSignInError() {
const searchParams = useSearchParams();
const error = searchParams.get('error');
if (!error) return null;
return (
<div className='text-center p-2 text-red-600 my-2'>
出了点问题!{error}
</div>
);
}
注意我们不打算编写更好的错误消息,这超出了本系列的范围。我们将此组件添加到 <SignIn />
组件中,完成:
//...
<GoogleSignInButton />
<GoogleSignInError />
// ...
在 NextAuth 中处理 Strapi 错误
有一件事我们还没有检查。在本文前面部分,我们提到我们在应用程序中发出了 4 个请求:登录和登出、Google OAuth 和 Strapi。我们还没有测试 Strapi 错误是否得到了处理。
在 authOptions
中,GoogleProvider,移除之前我们设置为 foobar
的错误 googleClient
,以引发错误。然后,为了从 Strapi 引发错误,我们将一个随机字符串作为 access_token
(我们通常从 Google OAuth 获得该令牌)。
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
//`${process.env.STRAPI_BACKEND_URL}/api/auth/${account.provider}/callback?access_token=${account.access_token}`,
`${process.env.STRAPI_BACKEND_URL}/api/auth/${account.provider}/callback?access_token=foobar`,
我们期待什么?在 URL 中出现一些类型的 NextAuth 错误代码。我们运行应用程序并尝试登录。果然如预期,URL 中出现了一个错误:error=Callback
。在文档中查找这个错误:
Callback: Error in the OAuth callback handler route
此外,我们还在终端中得到了真正的错误:
[next-auth][error][OAUTH_CALLBACK_HANDLER_ERROR]
https://next-auth.js.org/errors#oauth_callback_handler_error 400 Bad Request {
message: '400 Bad Request',
...
}
[OAUTH_CALLBACK_HANDLER_ERROR]
是 NextAuth 处理此错误的方式。400 Bad Request
来自 Strapi:我们在此处抛出了它 throw new Error(strapiError.error.message);
。
因此,我们已经处理了潜在的 Strapi 错误。
结论
我们学习了 NextAuth 如何处理错误。在大多数情况下,它只是将错误代码添加到登录页面的 URL 中:?error=
。在某些情况下,它会重定向到默认或自定义的 NextAuth 错误页面,同样带有错误代码。你可以在文档中查找这些代码,并使用错误代码为用户提供一些错误反馈。
此外,NextAuth 还会在你的 Next 服务器的终端中记录更详细的错误信息。这些错误也有特定的错误名称,你可以在文档中查找。然而,你不能将这些错误用于用户反馈。
如果我们从更实际的角度来看,显然 NextAuth 处理了所有错误。我们不必捕捉任何东西。这很好。NextAuth 有意在前端限制了错误信息。这可能会有点让人沮丧,并且需要一些努力来处理。但是,最终,NextAuth 非常稳定。使用 GoogleProvider 时获取错误应该是罕见的,并且这些错误现在已经得到了处理。
这就结束了我们与 GoogleProvider 的工作。在本系列的其余部分,我们将处理凭据提供程序。
首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。