SSO 使用户只需使用一组凭据即可安全地对多个应用程序和网站进行身份验证。在本教程中,您将学习如何在 Remix 中实现 SSO。
单点登录 (SSO) 是一种身份验证方案,使用户能够仅使用一组凭据(例如,电子邮件和密码)对多个应用程序进行身份验证。如果您使用文档、表格、云端硬盘等 Google 服务,您可能熟悉 SSO。您登录 Google 文档,当您访问云端硬盘或表格时,您会自动登录。
换句话说,单点登录允许用户登录一次并访问服务/应用程序,而无需重新输入身份验证凭据。
SSO 是通过拥有一个中央身份/身份验证服务器来实现的,通过该服务器执行身份验证,然后与其他域/服务共享会话,而无需重复请求登录凭据。当用户通过身份验证时,每个服务/域都将获得一个单独的令牌或 Cookie。只要存在与身份服务器的经过身份验证的会话,用户就无需重新输入其登录凭据。
在本教程中,我们将使用 Clerk 实现单点登录,并使用 GitHub 作为验证用户凭据的 OAuth 服务器。我们将修改现有应用程序以添加受保护的路由并使用预构建的 UI 组件。
Clerk 是一个 SSO 提供商,其集成可帮助您在几分钟内(如果您有复杂的用例,则为数小时)安全地采用和实施应用程序的单点登录身份验证方案。它具有GitHub,Twitter,Apple等的集成。
虽然可以使用OpenID Connect,OAuth和SAML等协议进行SSO流,但Clerk目前仅使用OAuth协议。它们提供 SDK 和 UI 组件以加快开发时间。
让我们从创建一个 Clerk 应用程序开始。
Clerk 应用是一种隔离特定应用程序的数据和配置的方法。
要创建应用程序,请转到 Clerk 仪表板,然后单击添加应用程序卡。您将看到可用身份验证选项的子集(您可以稍后更改它们):
输入您的应用程序名称,然后单击“社交关系”部分中的“显示更多”按钮以查看其他可用的 SSO 提供程序。
取消选择 Google 并选择 GitHub,然后单击页面底部的“完成”按钮。应用程序已创建,您将转到其主页。
在页面顶部,您应该会看到一个显示“实例”的菜单,可用于在应用程序的开发和生产环境之间切换。我们将致力于开发环境。
下一步是将 Clerk 与 GitHub 作为 OAuth 应用程序连接起来。Clerk 在开发模式下使用预配置的共享 OAuth 凭据和回调 URI,但这是可选的。需要使用 OAuth 凭据并在生产环境中重定向 URI。您将在本教程中使用开发环境,因此,您无需创建 GitHub OAuth 应用程序。
你将克隆一个 GitHub 存储库,该存储库将用作示例应用程序的样板。存储库可以在 https://github.com/pmbanugo/remix-sso-clerk.dev 找到。
您可以在 GitHub 代码空间中 Fork 并打开代码,或者打开终端并运行以下命令:
git clone https://github.com/pmbanugo/remix-sso-clerk.dev.git
cd remix-sso-clerk.dev
npm install
如果您使用 GitHub CLI,该命令
gh repo clone pmbanugo/remix-sso-clerk.dev
将为您实现相同的效果。
接下来,您需要安装 Clerk 的 Remix SDK。这使您可以访问其预构建的组件和 React 钩子,以及 Remix 加载器的助手。运行命令 npm install @clerk/remix
以安装 SDK。
下一步是添加 SDK 将使用的环境变量。为此,请将一个名为 .env 的新文件添加到项目的根目录中。然后将您的书记员密钥添加到文件中。
CLERK_PUBLISHABLE_KEY=your_publishable_key
CLERK_SECRET_KEY=your_secret_key
要获取相应的 Secret,请转到“Clerk”仪表板中的“API 密钥”页面。您将找到不同机密的不同部分。复制它们并更新 .env 文件中的值。
有了环境变量后,下一步是配置应用程序,以便可以从任何路由访问身份验证状态。
打开 app/root.tsx 文件。添加以下导入语句和加载程序。
import type { LoaderFunction } from "@remix-run/node";
import { rootAuthLoader } from "@clerk/remix/ssr.server";
export const loader: LoaderFunction = (args) => rootAuthLoader(args);
接下来,从 App 函数定义中删除 export default 关键字,并将其包装为 ClerkApp 高阶组件。
//Add this import statement as well
import { ClerkApp } from "@clerk/remix";
function App() {
return (
<Document>
<Layout>
<Outlet />
</Layout>
</Document>
);
}
export default ClerkApp(App);
Clerk 提供了一个 ClerkApp HOC,用于为您的 React 树提供身份验证状态。默认情况下,Clerk 使用生存期较短的无状态 JWT 令牌,并使用 Remix 的 CatchBoundary 来刷新过期的身份验证令牌。
将现有的 CatchBoundary() in app/root.tsx 重命名 CatchAll() 为 并导出一个新 CatchBoundary 函数,如下所示:
// Updated the import statement for ClerkCatchBoundary
import { ClerkApp, ClerkCatchBoundary } from "@clerk/remix";
// renamed to CatchAll
function CatchAll() {
const caught = useCatch();
let message;
//... the rest of the exiting function's logic
}
export const CatchBoundary = ClerkCatchBoundary(CatchAll);
现在我们进入有趣的部分, 验证用户 .我们将使用 Clerk 提供的一些 UI 组件。您可以根据需要设置它们的样式,但为了本教程,我们将使用默认样式。
您不限于使用提供的组件。您还可以构建自己的 UI 组件并使用 SDK 中提供的挂钩。您可以在文档中阅读有关使用钩子的更多信息。
让我们开始对用户进行身份验证。
我们将向导航栏添加 and 组件,这些组件将用于登录和注销用户。在 root.tsx 文件仍处于打开状态时,添加以下导入语句:
import {
ClerkApp,
ClerkCatchBoundary,
SignedIn,
SignedOut,
SignInButton,
SignOutButton,
} from "@clerk/remix";
滚动到第 118 行,其中有导航栏的列表项,然后将以下标记添加到列表中。
<li>
<SignedOut>
<SignInButton mode="modal" />
</SignedOut>
<SignedIn>
<SignOutButton />
</SignedIn>
</li>
如果用户注销, 该组件将显示其子项,并且 该组件也会执行相同的操作,但仅适用于登录用户。单击 时 ,它将显示登录模式。
到目前为止,我们已经使用组件根据登录状态显示或隐藏某些 UI 元素。在某些情况下,我们希望保护某些路由,并将未经身份验证的用户重定向到登录页面。您可以在加载程序或操作中使用该 getAuth() 函数来检查返回的用户是否为 null。如果是,则表示用户已注销。
我们将添加一个新 /demos/protected 路由,如果用户尚未登录,则会将用户重定向到登录页面。要实现这一点,请创建一个新的文件 app/routes/demos/signin.tsx 并将下面的代码粘贴到其中。
import { SignIn } from "@clerk/remix";
export default function LogIn() {
return <SignIn />;
}
这将从 Clerk 呈现 组件。
添加新的文件 routes/demos/protected.tsx 并将以下代码粘贴到其中:
import { getAuth } from "@clerk/remix/ssr.server";
import type { LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export const loader: LoaderFunction = async (args) => {
const { userId } = await getAuth(args);
if (!userId) {
return redirect("/demos/signin");
}
return json({ userId });
};
export default function Protected() {
const data = useLoaderData<typeof loader>();
return <h1>You're in! UserId is {data.userId}</h1>;
}
加载程序函数检查是否存在 userId .如果有,它将返回 userId 并显示消息。如果没有,它将重定向到 /demos/signin 路由。
这篇文章介绍了什么是单点登录以及如何使用 GitHub 和 Clerk 实现它。Clerk 是一个 SSO 提供程序,其集成可帮助您在更短的时间内安全地采用和实施应用程序的单点登录身份验证方案。
我们在本教程中使用了 UI 组件,但您可以使用自定义 UI 组件和 Clerk SDK 中的 React 钩子执行相同的操作。您可以在 SDK 参考文档中阅读有关使用挂钩的更多信息。