59.实战篇Clerk与登录注册
前言
登录与注册,一个说简单也简单,说复杂也复杂的功能。
往简单的说,注册的时候搞两个输入框,将数据写入数据库,登录的时候校验一下数据是否正确即可。
往复杂的说,除了登录注册,还有账号注销、忘记密码、密码重置、邮箱验证、更新个人资料、更新密码、删除账号等功能,此外,数据的安全性如何保证?Magic Links、Multi-Factor Auth (MFA)、社交媒体登录 (Google, Facebook, Twitter, GitHub, Apple, and more) 是否要支持?是不是还要做个后台统计用户登录数据?想一想都是工作量。
所以虽然可以自己从头做,但有现成的还是用现成的吧。
所幸关于登录注册的技术方案,并不像评论系统那么多,推荐 3 个主流的技术选型:
The most comprehensive User Management Platform
Clerk 提供了一个开发人员友好的身份验证和用户管理解决方案,帮助开发者轻松构建和管理用户身份验证、用户账户和权限管理功能。它提供了安全的身份验证、社交登录集成、角色和权限管理等功能。
Supabase is an open source Firebase alternative.
Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.
简单来说,Supabase 是 Firebase 的开源替代品,属于 BaaS(后端即服务)产品。所谓 BaaS,开发者只需要开发和维护前端代码,由 BaaS 服务商提供了开发应用所需要的后端服务,如用户身份验证、数据库管理、推送通知(针对移动应用程序),以及云存储和托管等。
此外,Supabase 建立在 Postgres 之上。Postgres 是一个免费的开源数据库,被认为是世界上最稳定、最先进的数据库之一。
所以用户身份验证只是 Supabase 提供的功能之一。
如果要比较 Clerk 和 Supabse 的话,Clerk 更专注于身份验证和用户管理,对应功能更加丰富。Supabase 实现的功能更多,身份验证只是其中之一。
实际使用的时候,两者也可以一起使用。Clerk 和 Supabse 各自提供了接入对方的文档:
注意:这两个平台都提供了免费版,但免费版毕竟有一些限制,比如 Clerk 会限制 10000 月活跃用户等,Supabse 会限制 50000 月活跃用户,数据库大小为 500 MB 等等。
我们在《实战篇 | React Notes | next-auth》介绍的便是 Next-Auth,Next-auth 不是平台,是一个开源库,可以帮助我们快速实现登录注册等功能。
总的来说,如果要快速接入登录注册功能,最好还是使用平台,也就是 Clerk 和 Supabse,其中 Clerk 提供的功能更为丰富,但免费版月活限制用户数更少。
本篇我们对 Clerk 的使用进行讲解。
Clerk
1. Clerk 注册并创建应用
打开 https://clerk.com/ 注册一个账号,首次登录后会进入创建应用界面:
输入应用的名字,左边选择支持的登录方式,右边为预览登录界面 UI,样式会根据左边的选择有所不同。
创建应用后,会跳转到 Get started 页面,指导用户如何接入 Clerk:
2. 接入 Clerk
为了演示如何接入 Clerk,我们使用官方脚手架创建一个新项目:
npx create-next-app@latest
项目安装依赖项:
npm install @clerk/nextjs
添加本地环境变量:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxxx
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxx
新建 middleware.js
,代码如下:
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
修改 app/layout.jsx
,代码如下:
import {
ClerkProvider,
SignInButton,
SignedIn,
SignedOut,
UserButton
} from '@clerk/nextjs'
import './globals.css'
export default function RootLayout({
children,
}) {
return (
<ClerkProvider>
<html lang="en">
<body>
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
{children}
</body>
</html>
</ClerkProvider>
)
}
解释下这段代码中用到的组件:
所有 Clerk hooks 和组件都必须是 <ClerkProvider>
的子组件,所以我们用 <ClerkProvider>
将整个页面代码包裹,该组件会存储 session 和用户上下文数据。
<SignedIn>
下的子组件仅在登录状态时显示。<SignedOut>
下的子组件仅在非登录状态时显示。
<SignInButton>
是一个链接至登录页面的无样式组件。<UserButton>
是一个 Clerk 提供的自带样式的组件,用于展示用户头像。
此时我们就实现了 Clerk 的接入,并已经实现了账号的登录、注册、账号管理、注销等功能。
打开 http://localhost:3000/,浏览器效果如下:
3. 添加中文
从上图可以发现,虽然 Clerk 实现了登录注册的功能,但有一个问题,那就是界面都是英文,如何改成中文界面呢?Clerk 也考虑到了这一点,并提供了本地化方法。
安装依赖项:
npm install @clerk/localizations
修改 app/layout.js
,代码如下:
import {
ClerkProvider,
SignInButton,
SignedIn,
SignedOut,
UserButton
} from '@clerk/nextjs'
import './globals.css'
import { zhCN } from "@clerk/localizations";
export default function RootLayout({
children,
}) {
return (
<ClerkProvider localization={zhCN}>
<html lang="zh-CN">
<body>
<SignedOut>
<SignInButton mode="modal">
登录
</SignInButton>
</SignedOut>
<SignedIn>
<UserButton showName={true} />
</SignedIn>
{children}
</body>
</html>
</ClerkProvider>
)
}
此时浏览器效果如下:
注意:这里<SignInButton>
的 mode
设置为了 "modal"
才让登录界面的本地化生效。如果不设置为 modal 则会直接跳转到登录界面,此时本地化不会生效。如果让跳转登录界面也实现本地化的话,可以参考接下来讲到的 Next-js-Boilerplate 的实现方式。
4. 后台界面
Clerk 提供了后台界面,可以查看登录用户数据以及进行登录相关的设置:
当然,Clerk 实现的功能远不止如此,你可以完全自定义登录界面、使用内置的组件自定义开发、构架自己的登录流程等等,Clerk 都提供了详细的文档:https://clerk.com/docs
Next-js-Boilerplate
既然我们已经知道了 Next.js 项目如何接入 Clerk,那回到我们之前讲到的 Next-js-Boilerplate 模板。
1. 基础设置
在这个模板中使用 Clerk,最基础的就是设置 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
和 CLERK_SECRET_KEY
。
从安全的角度来讲,应该将
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
放到.env
文件中,CLERK_SECRET_KEY
放到.env.local
文件中,并且 Git 不提交.env.local
2. 登录路由
但与之前简易接入 Clerk 的方式不同,在 Next-js-Boilerplate 中,当用户点击 Sign In 的时候,跳转的并不是 Clerk 的登录页面,而是 http://localhost:3000/sign-in,这是因为在 .env
中,我们通过 CLERK_SIGN_IN_URL
环境变量重新设置了登录的跳转地址:
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
查看 /sign-in
路由对应的文件代码,打开 src/app/[locale]/(auth)/(center)/sign-in/[[...sign-in]]/page.tsx
:
import { SignIn } from '@clerk/nextjs';
import { getTranslations } from 'next-intl/server';
import { getI18nPath } from '@/utils/Helpers';
// ...
const SignInPage = (props: { params: { locale: string } }) => (
<SignIn path={getI18nPath('/sign-in', props.params.locale)} />
);
export default SignInPage;
在这个页面路由中,我们使用了 Clerk 提供的 <SignIn>
组件来渲染登录界面,其中 path
属性用来设置组件挂载的路径。
3. 中文设置
如果我们的 Next-js-Boilerplate 要实现默认中文登录界面的效果,该如何实现呢?
首先,修改 src/utils/AppConfig.ts
,完整代码如下:
import type { LocalePrefix } from 'node_modules/next-intl/dist/types/src/shared/types';
const localePrefix: LocalePrefix = 'as-needed';
export const AppConfig = {
name: 'Nextjs Starter',
locales: ['zh', 'en'],
defaultLocale: 'zh',
localePrefix,
};
然后修改 src/app/[locale]/(auth)/layout.tsx
,完整代码如下:
import { enUS, zhCN } from '@clerk/localizations';
import { ClerkProvider } from '@clerk/nextjs';
export default function AuthLayout(props: {
children: React.ReactNode;
params: { locale: string };
}) {
let clerkLocale = zhCN;
let signInUrl = '/sign-in';
let signUpUrl = '/sign-up';
let dashboardUrl = '/dashboard';
if (props.params.locale === 'en') {
clerkLocale = enUS;
}
if (props.params.locale !== 'zh') {
signInUrl = `/${props.params.locale}${signInUrl}`;
signUpUrl = `/${props.params.locale}${signUpUrl}`;
dashboardUrl = `/${props.params.locale}${dashboardUrl}`;
}
return (
<ClerkProvider
localization={clerkLocale}
signInUrl={signInUrl}
signUpUrl={signUpUrl}
signInFallbackRedirectUrl={dashboardUrl}
signUpFallbackRedirectUrl={dashboardUrl}
>
{props.children}
</ClerkProvider>
);
}
最后,添加翻译文件,新建 src/locales/zh.json
,代码如下:
{
"RootLayout": {
"home_link": "主页",
"about_link": "关于",
"guestbook_link": "Guestbook",
"portfolio_link": "Portfolio",
"sign_in_link": "登录",
"sign_up_link": "注册"
},
"BaseTemplate": {
"description": "Starter code for your Nextjs Boilerplate with Tailwind CSS",
"made_with": "Made with"
},
"Index": {
"meta_title": "Next.js Boilerplate Presentation",
"meta_description": "Next js Boilerplate is the perfect starter code for your project. Build your React application with the Next.js framework."
},
"Guestbook": {
"meta_title": "Guestbook",
"meta_description": "An example of CRUD operation",
"database_powered_by": "Database powered by",
"loading_guestbook": "Loading guestbook...",
"error_reporting_powered_by": "Error reporting powered by"
},
"GuestbookForm": {
"username": "Username",
"body": "Body",
"save": "Save"
},
"About": {
"meta_title": "About",
"meta_description": "About page description",
"about_paragraph": "Welcome to our About page! We are a team of passionate individuals dedicated to creating amazing software.",
"translation_powered_by": "Translation powered by"
},
"Portfolio": {
"meta_title": "Portfolio",
"meta_description": "Welcome to my portfolio page!",
"presentation": "Welcome to my portfolio page! Here you will find a carefully curated collection of my work and accomplishments. Through this portfolio, I'm to showcase my expertise, creativity, and the value I can bring to your projects.",
"portfolio_name": "Portfolio {name}",
"error_reporting_powered_by": "Error reporting powered by",
"coverage_powered_by": "Code coverage powered by"
},
"PortfolioSlug": {
"meta_title": "Portfolio {slug}",
"meta_description": "Portfolio {slug} description",
"header": "Portfolio {slug}",
"content": "Created a set of promotional materials and branding elements for a corporate event. Crafted a visually unified theme, encompassing a logo, posters, banners, and digital assets. Integrated the client's brand identity while infusing it with a contemporary and innovative approach. Garnered favorable responses from event attendees, resulting in a successful event with heightened participant engagement and increased brand visibility.",
"log_management_powered_by": "Log management powered by"
},
"SignIn": {
"meta_title": "Sign in",
"meta_description": "Seamlessly sign in to your account with our user-friendly login process."
},
"SignUp": {
"meta_title": "Sign up",
"meta_description": "Effortlessly create an account through our intuitive sign-up process."
},
"Dashboard": {
"meta_title": "Dashboard",
"hello_message": "Hello {email}!"
},
"UserProfile": {
"meta_title": "User Profile"
},
"DashboardLayout": {
"dashboard_link": "Dashboard",
"user_profile_link": "Manage your account",
"sign_out": "Sign out"
}
}
当然在这里我们只翻译了登录相关的文字,其他内容的翻译我们会在下篇借助 CrowdIn 来实现。
此时浏览器效果如下:
总结
登录注册所代表的鉴权功能,主流技术选型有 3 个:Clerk、Supabase 和 Next-Auth。其不同之处在于 Clerk 专注于用户管理,Supabse 是 BaaS 产品,鉴权是其中一个功能。Next-Auth 是一个开源库,需要自己实现数据库等。
总的来说,为了快速接入,会优先选择接入 Clerk 和 Supabase,这两个平台自带数据库实现,但免费版都有一些限制。但两者也不冲突,Clerk 和 Supabse 可以一起使用。