跳到主要内容

59.实战篇 _ Clerk 与登录注册

前言

登录与注册,一个说简单也简单,说复杂也复杂的功能。

往简单的说,注册的时候搞两个输入框,将数据写入数据库,登录的时候校验一下数据是否正确即可。

往复杂的说,除了登录注册,还有账号注销、忘记密码、密码重置、邮箱验证、更新个人资料、更新密码、删除账号等功能,此外,数据的安全性如何保证?Magic Links、Multi-Factor Auth (MFA)、社交媒体登录 (Google, Facebook, Twitter, GitHub, Apple, and more) 是否要支持?是不是还要做个后台统计用户登录数据?想一想都是工作量。

所以虽然可以自己从头做,但有现成的还是用现成的吧。

所幸关于登录注册的技术方案,并不像评论系统那么多,推荐 3 个主流的技术选型:

  1. Clerk

The most comprehensive User Management Platform

Clerk 提供了一个开发人员友好的身份验证和用户管理解决方案,帮助开发者轻松构建和管理用户身份验证、用户账户和权限管理功能。它提供了安全的身份验证、社交登录集成、角色和权限管理等功能。

  1. Supabase

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 各自提供了接入对方的文档:

  1. Clerk:Integrate Supabase with Clerk
  2. Supabase:Integrate Clerk

注意:这两个平台都提供了免费版,但免费版毕竟有一些限制,比如 Clerk 会限制 10000 月活跃用户等,Supabse 会限制 50000 月活跃用户,数据库大小为 500 MB 等等。

  1. Next-Auth

我们在《实战篇 | React Notes | next-auth》介绍的便是 Next-Auth,Next-auth 不是平台,是一个开源库,可以帮助我们快速实现登录注册等功能。

总的来说,如果要快速接入登录注册功能,最好还是使用平台,也就是 Clerk 和 Supabse,其中 Clerk 提供的功能更为丰富,但免费版月活限制用户数更少。

本篇我们对 Clerk 的使用进行讲解。

Clerk

1. Clerk 注册并创建应用

打开 https://clerk.com/ 注册一个账号,首次登录后会进入创建应用界面:

image.png

输入应用的名字,左边选择支持的登录方式,右边为预览登录界面 UI,样式会根据左边的选择有所不同。

创建应用后,会跳转到 Get started 页面,指导用户如何接入 Clerk:

image.png

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,浏览器效果如下:

4.gif

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>
)
}

此时浏览器效果如下:

5.gif

注意:这里<SignInButton>mode 设置为了 "modal" 才让登录界面的本地化生效。如果不设置为 modal 则会直接跳转到登录界面,此时本地化不会生效。如果让跳转登录界面也实现本地化的话,可以参考接下来讲到的 Next-js-Boilerplate 的实现方式。

4. 后台界面

Clerk 提供了后台界面,可以查看登录用户数据以及进行登录相关的设置:

image.png

当然,Clerk 实现的功能远不止如此,你可以完全自定义登录界面、使用内置的组件自定义开发、构架自己的登录流程等等,Clerk 都提供了详细的文档:https://clerk.com/docs

Next-js-Boilerplate

既然我们已经知道了 Next.js 项目如何接入 Clerk,那回到我们之前讲到的 Next-js-Boilerplate 模板。

1. 基础设置

在这个模板中使用 Clerk,最基础的就是设置 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_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 来实现。

此时浏览器效果如下:

6.gif

总结

登录注册所代表的鉴权功能,主流技术选型有 3 个:Clerk、Supabase 和 Next-Auth。其不同之处在于 Clerk 专注于用户管理,Supabse 是 BaaS 产品,鉴权是其中一个功能。Next-Auth 是一个开源库,需要自己实现数据库等。

总的来说,为了快速接入,会优先选择接入 Clerk 和 Supabase,这两个平台自带数据库实现,但免费版都有一些限制。但两者也不冲突,Clerk 和 Supabse 可以一起使用。