ClerkのReact Componentが便利すぎるので覗いてみる
今回は認証・認可サービスであるClerkのSDKについて解説をします。Clerkといえば基本的な認証・認可機能はもちろんのこと提供されているReact Componentを配置するだけで認証フローが完結する特徴があります。
しかし、React Componentを配置するだけで認証が完結するというのはあまりにも簡単でブラックボックス化しているため、内部の実装を確認してその詳細を見ていきたいと思います。
はじめに
本記事ではClerkの提供するSDKに着目して解説をするため、具体的なClerkの使用方法や個別の認証方法については触れません。
また、Next.jsやRemix、TanStack StartなどさまざまなFrameworkに対応していますが、今回はReact + Viteの構成を前提とします。
SDKのGithub Repositoryは下記のリンクです。バージョンは5.9.1を参照します。
Quickstart
まずは公式ドキュメントをもとに最もシンプルにClerkを始める流れを整理します。
ライブラリのInstallと環境変数の設定
Installするべきパッケージは@clerk/clerk-react
のみです。
pnpm add @clerk/clerk-react
@clerk/clerk-react
は内部でSWRを使用していることや共通のShared Packageによって圧縮後でも32.3kBと少し大きめの印象です。
Packageインストール後は必要な環境変数を設定します。VITE_CLERK_PUBLISHABLE_KEYはClerkのダッシュボードから取得が可能です。
VITE_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
ClerkProvider
を追加する
これまでで必要な設定は完了しました。次にClerkProvider
でアプリケーションをWrapします。その際に先ほど設定したPUBLISHABLE_KEYをProviderに渡します。
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { ClerkProvider } from '@clerk/clerk-react'
// Import your publishable key
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
if (!PUBLISHABLE_KEY) {
throw new Error('Missing Publishable Key')
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
<App />
</ClerkProvider>
</React.StrictMode>,
)
HeaderにClerkのComponentを配置する
*公式ドキュメントとコード例を変更しています。
最後にアプリのHeaderにClerkのComponentを配置します。<SignedIn>
はユーザがアプリケーションにログインしている場合のみChildrenをレンダリングします。
逆に<SignedOut>
はログインしているユーザがいない場合にのみChildrenをレンダリングします。そのため、未ログイン時は<RedirectToSignIn>
がレンダリングされ、自動的にSignInページへリダイレクトされます。
import { SignedIn, SignedOut, RedirectToSignIn, UserButton } from '@clerk/clerk-react'
export default function App() {
return (
<header>
{/* ⭐️ 未ログイン時は↓がレンダリングされ、SignInページへリダイレクトされる */}
<SignedOut>
<RedirectToSignIn />
</SignedOut>
{/* ⭐️ ログイン時は↓がレンダリングされる */}
<SignedIn>
<UserButton />
</SignedIn>
</header>
)
}
その後SignInページへ遷移しますが、SignInのPortalなどはあらかじめClerkが用意してくれているため、このような認証画面などの実装も一切不要です。
https://clerk.com/docs/customization/account-portal/overview#sign-in
また、追加の要件が発生してもClerkのDashboardを変更するか、必要なComponentを追加するだけで認証基盤を拡張させることができます。
ClerkProviderとHooks
これだけ便利なClerkですが内部でどのような実装がされているのかが気になるところです。そこで、上記で解説をしたQuickStarkの実装をソースコードを見ながら確認していきます。
冒頭でも紹介した通りClerkのSDKを提供しているRepositoryは下記のものです。その中のpackages/react配下に@clerk/clerk-react
に関する実装があります。
ClerkProviderが管理するもの
ClerkProviderは以下のContextを管理するProviderです。基本的にはReactのcreateContextを使用してGlobalなStateを管理しています。管理されているContextは以下の6つです。
{/* packages/shared/src/react/contexts.tsxにて管理 */}
const [UserContext, useUserContext] = createContextAndHook<UserResource | null | undefined>('UserContext');
<IsomorphicClerkContext.Provider value={clerkCtx}>
<ClientContext.Provider value={clientCtx}>
<SessionContext.Provider value={sessionCtx}>
<OrganizationProvider {...organizationCtx.value}>
<AuthContext.Provider value={authCtx}>
<UserContext.Provider value={userCtx}>{children}</UserContext.Provider>
</AuthContext.Provider>
</OrganizationProvider>
</SessionContext.Provider>
</ClientContext.Provider>
</IsomorphicClerkContext.Provider>
IsomorphicClerkContext
- これはメインのClerk SDK Objectです
- versionからmetadataなど様々管理します
- Source Code: https://github.com/clerk/javascript/blob/5e0da19123b585d0cbf502f3138076be6c4c126f/packages/types/src/clerk.ts#L89
- Docs: https://clerk.com/docs/references/javascript/clerk/clerk
ClientContext
- これは現在認証済みのデバイスを追跡するためのObjectです
- それ以外にもSignInやSignOutの進捗状況に関しても管理します
- Docs: https://clerk.com/docs/references/javascript/client
SessionContext
- これはHTTPセッションを抽象化したObjectです
- セッションのアクティビティを記録し、クライアントサイドでセッションを終了するためのメソッドなどが含まれています
- Docs: https://clerk.com/docs/references/javascript/session
OrganizationProvider
- これはOrganizationに関する情報とそれを管理するためのObjectです
- Docs: https://clerk.com/docs/references/javascript/organization/organization
AuthContext
- これはuserIdやorgId、orgRoleなど認証・認可に関する情報を管理するためのObjectです
- Souce Code: https://github.com/clerk/javascript/blob/5e0da19123b585d0cbf502f3138076be6c4c126f/packages/react/src/contexts/AuthContext.ts
UserContext
- これはユーザに関するすべての情報を管理するObjectです
- Docs: https://clerk.com/docs/references/javascript/user/user
Contextを扱うためのHooks
先ほどClerkProviderによって管理される状態を整理しました。次にそれらを扱うHooksを見ていきます。基本的には単純にContextを取得して返すだけですが、useOrganization
は内部でロジックを持っています。
useOrganization
OrganizationのProviderを見るとわかるとおりSWRのConfigが設定されています。Organizationの管理においてはデータ取得にSWRが使用されているようです。
例えばuseOrganizationを使用して、Organizationに紐づくmembershipを取得する際などは内部でSWRを使ってデータを取得しています。
*usePagesOrInfiniteは内部でSWRをWrapした関数です。
開発者がClerkを使用する際に直接SWRを意識することはないようにWrapされていますが、SWRのキャッシュなどで何か特殊な挙動に疑問を持った際には参考になるかもしれません。
また、Contextからデータを取得するだけだと思いながらも、意図せずHTTPリクエストが走っている可能性があるので注意が必要です。
各種ComponentとRouter
先ほどClerkが提供するProviderからどのような状態が管理されているかを解説しました。次にQuickStartで紹介したUI Componentがどのように実装され、認証が行われるかを見ていきます。
import { SignedIn, SignedOut, RedirectToSignIn, UserButton } from '@clerk/clerk-react'
export default function App() {
return (
<header>
<SignedOut>
<RedirectToSignIn />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
)
}
SignedInとSignedOut
先ほどの例で<SignedIn>
と<SignedOut>
はユーザのログイン状態に応じてChildrenを制御するとありましたが、Contextからユーザ情報を取得してレンダリングを制御しているだけです。
そしてユーザが未ログイン状態の場合に<RedirectToSignIn />
がレンダリングされてSignInページへリダイレクトされます。
SignInコンポーネント
https://clerk.com/docs/customization/account-portal/overview#sign-in
冒頭でも掲載しましたが、ClerkではこのようなSignIn・SiginUp画面がデフォルトで用意されており開発者が実装を意識することなく配置・カスタマイズができます。
先ほどの例に続き、<RedirectToSignIn />
がレンダリングされて/sign-inへリダイレクトされたことを想定します。
SignInに関するComponentはpackages/clerk-js配下に実装されており、独自のRouterによってnavigationが制御されています。Clerkの独自RouterはInterfaceがReact Routerに似ており、シンプルで薄く実装がされています。
SignIn.tsxではログインで使用されうる認証画面のルーティングを定義しています。例えば他要素認証の場合<SignInStart />
でパスワード認証をしたのちに、/sign-in/factor-twoへ遷移して<SignInFactorTwo />
をレンダリングするといった具合です。
function SignInRoutes(): JSX.Element {
const signInContext = useSignInContext();
return (
<Flow.Root flow='signIn'>
<Switch>
<Route path='factor-one'>
<SignInFactorOne />
</Route>
<Route path='factor-two'>
<SignInFactorTwo />
</Route>
<Route path='reset-password'>
<ResetPassword />
</Route>
{/* ...省略 */ }
<Route index>
<SignInStart />
</Route>
<Route>
<RedirectToSignIn />
</Route>
</Switch>
</Flow.Root>
);
}
SignInStart
/sign-inへ遷移してindexルートの場合レンダリングされるのは<SignInStart>
です。
このコンポーネントが実際のSignIn画面に該当する実装をしています。
下のコードは認証情報が入力されてFormがSubmitされた際に発火する関数です。
signIn.create()
によってサインイン処理が実行され、statusが"complete"の場合はユーザをActiveにし、追加で認証が必要な場合は先ほど設定したルーティングに従って遷移させています。
そして認証に成功すると<SignedIn>
内のchildrenがレンダリングされて認証後の画面が表示されます。これでQueickStartの一連の流れが完了しました。
まとめ
今回は簡単ではありますがQuickStartの挙動が内部でどのように実装されているのか、Clerkが提供するSDKの内部実装を見ていきました。冒頭で紹介したようにClerkはReactのComponentを配置するだけで認証が実装できてしまいます。
その便利さゆえに内部がどのように管理されているか気になったため覗いてみましたが、SDKのpackage全体を通して非常に見通しがよくわかりやすいコードだったと感じました。
最後に
AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
Discussion