はじめに
今回ですが、Next.js 15とClerkという認証系SaaSを使用して、5分で認証機能を開発していこうと思います。
ハンズオンでも進められる内容になっているので、試してみたい方は事前にClerkにて、アカウント登録を行っておいてください。
今回のソースですが、以下から閲覧可能です。
Clerkとは
Clerkは認証系の機能を提供してくれる認証系のSaaSです。
認証に必要なものは、ほぼすべて提供してくるため、認証の実装を簡単に記述できたり、Clerk内で、認証ユーザーの管理を行えたりと、面倒かつ煩雑な認証機能を丸投げすることができます。
私が使用してみて感じたメリットは以下です。
- 認証用画面をコンポーネントで提供してくるため、認証画面はコピペで作成できる(細かいスタイリングは必要)
- ドキュメントに沿うだけで認証機能作れる(ほぼコピペでできます)
- OAuth(GitHubやGoogle認証など)はClerk側で自動で行ってくれる
- 自分でGCPやGitHubのsercret keyやcallback_uriの設定や発行が不要
- ユーザー情報の管理機能まで提供される(Clerkダッシュボード(管理画面)でもできますし、ユーザー自身が行うこともできます)
- email・passwordの二段階認証のUIまである
- メール送信ももちろん行われる
- 公式ドキュメントにはAI検索機能が搭載(これからはこういう公式ドキュメントが増えそう)
上記を自力で実装しようとすると、かなり大変だと思いますが、Clerkを使えば、これらの複雑な認証機能を5分で作れます。
アプリケーションのセットアップ
以下コマンドを実行します。
私はBunを使っていこうと思います。
bunx create-next-app@latest --typescript --tailwind
各オプションはデフォルトでよいですが、私の場合はESLintを使わずbiomeを使うため、そこだけNoを選択しました。
Clerkのセットアップ
まず、Clerk上でapplicationの作成を行っていきます。
使用したいSign in options
にをONにするだけで、OAuthの認証機能を使えます。
デフォルトでEmail・Googleの認証がONになっています。
今回、私はGitHubの認証もONにして使っていこうと思います。
こちらの設定が終わったら、Create application
をクリックします。
すると、以下の画面に遷移するため、手順に従っていきます。
ライブラリ追加
画面の手順に表示されるため、コピペでできます。
bun add @clerk/nextjs
環境変数設定
画面の手順に表示されるため、コピペでできます。
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=********************************
CLERK_SECRET_KEY=********************************
middlewareの作成
src
ディレクトリを使用している場合はsrc
配下にmiddleware.ts
を作成します。
src
ディレクトリを使用していない場合は、rootディレクトリ配下にmiddleware.ts
を作成します。
この手順も画面に表示されるため、コピペでできます。
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
}
JSON Web Tokens (JWT) を使用しており、Cookie に保存された JWT を検証することで認証を行います。
ちなみに、clerkMiddleware
ですが、以下の記事にあるように、JWTを使って検証を行うようです。
ClerkProviderの追加
app/layout.tsx
にて、ClerkProvider
を追加していきます。
こちらも画面の手順に表示されるため、コピペでできます。
import type { Metadata } from 'next'
import localFont from 'next/font/local'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'
import type { ReactNode } from 'react'
const geistSans = localFont({
src: './fonts/GeistVF.woff',
variable: '--font-geist-sans',
weight: '100 900',
})
const geistMono = localFont({
src: './fonts/GeistMonoVF.woff',
variable: '--font-geist-mono',
weight: '100 900',
})
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
const RootLayout = ({ children }: Readonly<{ children: ReactNode }>) => {
return (
<ClerkProvider>
<html lang="ja">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
</ClerkProvider>
)
}
export default RootLayout
ここまでで初回設定画完了です。
これでClerk
を使用できる準備が整いました。
Next.jsで認証機能を実装
ここから本格的に、認証機能を実装していきます。
以下を参考にして行っていきます。
sign-upページの作成
app/sign-up/[[...sign-up]]/page.tsx
を作成しています。
こちらはOptional Catch-all Segments
というsegmentで、/sign-up
以下のパスパラメータを何でも受け取るというようなsegmentになります。
以下のようにします。
fallbackRedirectUrl
に認証後にリダイレクトさせたいパスを渡します。
また、後ほど、RootLayoutの修正を行うめ、少しスタイリングを調整します。
import { SignUp } from '@clerk/nextjs'
const SignUpPage = () => {
return (
<div className="w-full mt-4 flex justify-center items-center">
<SignUp fallbackRedirectUrl={'/'} />
</div>
)
}
export default SignUpPage
sign-inページの作成
sign-upと同様にsign-inページについてもapp/sign-in/[[...sign-in]]/page.tsx
を作成し、同じようにコンポーネントを作成します。
import { SignIn } from '@clerk/nextjs'
const SignInPage = () => {
return (
<div className="w-full mt-4 flex justify-center items-center">
<SignIn fallbackRedirectUrl={'/'} />
</div>
)
}
export default SignInPage
middlewareを修正
以下のように、clerkMiddleware
に処理を追加し、sign-inとsign-up配下のパスへの遷移をpublicにします。
auth.protect()
はpublicでないパスに認証なしでアクセスした場合に、sign-inページにリダイレクトさせる処理です
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)'])
export default clerkMiddleware(async (auth, request) => {
// sign-in, sign-up 以外は認証ページにリダイレクト
if (!isPublicRoute(request)) {
await auth.protect()
}
})
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
}
環境変数を追加
ドキュメントに従い、以下の環境変数を追加していきます。
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
RootLyaoutの修正
以下のドキュメントを参照し、Clerkが提供するコンポーネントを組み込み、ヘッダーを作成していきます。
また、スタイルを少し調整します。
import type { Metadata } from 'next'
import localFont from 'next/font/local'
import './globals.css'
import {
ClerkProvider,
SignInButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs'
import type { ReactNode } from 'react'
const geistSans = localFont({
src: './fonts/GeistVF.woff',
variable: '--font-geist-sans',
weight: '100 900',
})
const geistMono = localFont({
src: './fonts/GeistMonoVF.woff',
variable: '--font-geist-mono',
weight: '100 900',
})
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
const RootLayout = ({ children }: Readonly<{ children: ReactNode }>) => {
return (
<ClerkProvider>
<html lang="ja">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<header className="fixed top-0 left-0 w-full z-50 h-16 bg-neutral-100 flex items-center">
<SignedOut>
<div className="h-fit ml-4 bg-blue-500 w-fit p-2 rounded-md text-white text-sm font-semibold">
<SignInButton />
</div>
</SignedOut>
<SignedIn>
<div className="h-fit ml-4 w-fit">
<UserButton />
</div>
</SignedIn>
</header>
<main className="min-h-[calc(100dvh-4rem)] max-w-full pt-16">
{children}
</main>
</body>
</html>
</ClerkProvider>
)
}
export default RootLayout
ここまでで、認証に必要なUIと機能の作成が完了しました。
画面を確認してみましょう!
sign-inページ
以下のようなUIがCardのような形式で表示されます。
これらは、今どきよく見られるSignIn画面と思います。
このあたりのUIもトレンドをおさえているので非常に、好感が持てます。
email・password認証
機能もしっかりしており、アカウントにないメールアドレス入力で弾かれるようになっています。
また、パスワードリセットもデフォルトであるようです。
試しに、リセットしてみましょう。
二段階認証も付いています。
コード再送信機能も当然のようについています!
また、この二段階認証のUIですが、自前で実装しようとすると、かなり大変です。
なので、コンポーネントライブラリに任せるのが一般的ですが、それでも大変です。
ただ、Clerkの場合、デフォルトでUXもしっかりしており、テンキーで動かせるので、そこもポイント高いです。
(この間、テンキーどころかカーソル当てても該当のinput要素にフォーカスされないシステムが有り、かなり悪い印象を植え付けられました)
二段階認証のコードについては、入力したメールアドレス宛に送られてきます。
パスワード再設定画面も完璧ですね!
入力すると、ログインされるみたいで、トップページに遷移します。
この辺のリダイレクトは要件にもよるので、ログインページに再度飛ばすこともできるとは思います。
(ドキュメントで調べてみてください)
sign-upページ
こちらもほぼ、sign-inページと同じですね。
こちらも試しにemail・passwordの認証を行おうと思います。
email・password認証
まずは、パスワードのチェックから見ていきましょう。
入力を行い、Submitすると、弾かれました。
文字数だけでなく、パスワードのセキュリティチェックも行ってくれるみたいです。
(これも自力でバリデーションしようとすると結構、面倒です。)
セキュアなパスワードを入力するとUIがセキュリティ条件をクリアしたことを知らせてくれます。
あとは、sign-inと同じ流れで、二段階認証の再送もできますし、二段階認証通れば、トップページにリダイレクトされます。
ユーザー情報管理機能の実装
ここでは認証済みユーザーに関連する処理を見ていきます。
まずは、ログインユーザー取得機能を追加していきます!
Server Component
以下を参照し、Server Componentからユーザーを取得してみましょう。
※ 後ほど、Client Componentからも取得する方法を記載するため、Client Componentを作成しています。
import { ClientComponent } from '@/components/client-component'
import { auth, currentUser } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
const Home = async () => {
const { userId } = await auth()
if (!userId) {
redirect('/sign-in')
}
const user = await currentUser()
return (
<div className="w-full h-full flex flex-col justify-center items-center mt-4 gap-2">
<p>
Hello <span className="font-semibold">{user?.fullName}</span>, this is a
server component
</p>
<div>
<ClientComponent />
</div>
</div>
)
}
export default Home
Client Component
以下、同じドキュメントのclient-sideの項目で、Client Componentでのユーザー取得を行っていきましょう!
(Server Component・Client Componentの両者の方法を紹介するため、今回はどちらも行っています)
以下のようにしますが、基本的には、useUser()
のみ使用すればよいと思います。
私は、userIdを表示するために、useAuth()
も使っています。
'use client'
import { Loader } from '@/components/loader'
import { useAuth, useUser } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
export const ClientComponent = () => {
const router = useRouter()
const { isLoaded, userId, sessionId } = useAuth()
const { isLoaded: isUserLoaded, isSignedIn, user } = useUser()
if (!(isLoaded && isUserLoaded)) {
return <Loader />
}
if (!(isSignedIn && user && userId)) {
router.push('/sign-in')
}
return (
<div className="flex flex-col gap-2 items-center">
<p>
Hello, <span className="font-semibold">{userId}</span> your current
current active session is{' '}
<span className="font-semibold">{sessionId}</span>
</p>
<p>
Nice to see you,{' '}
<span className="font-semibold">
{user?.firstName} {user?.lastName}
</span>
, welcome to the client component!
</p>
</div>
)
}
これで、ユーザー管理機能の実装も完了しました。
それでは、認証後のページを見ていきましょう。
以下のようになっており、認証後はヘッダーにアイコンが表示されているのがわかります。
Google認証を使用したため、Googleアカウントのアイコン画像が表示されています。
これがGitHubの認証を使用すれば、GitHubアカウントのアイコン画像が表示されますし、未認証ではAvatar Componentのように表示されます。
ユーザーボタン
ヘッダーのユーザーボタンをクリックすると以下のようなUIが表示されます。
これもClerk側が用意しており、こちらでは何も実装していないです。
それでは、Manage account
をクリックしてみましょう!
以下のようにユーザー編集UIが表示されました。
これで今回実装した、認証の内容は以上です。
最後にClerkのダッシュボードを見てみましょう!
以下のように管理画面でユーザー一覧が表示され、更新・削除などの管理機能を使えます。
おわりに
認証系SaaSのClerkどうでしょうか?
他にも認証系のことはほぼ網羅的に機能として提供されているので、かなりおすすめです!
料金プランもProプランで月額25ドルなので、これほどまでの認証系の機能を提供してくれているので、かなりお手軽かなと思います。
また、WebHookなどの設定もできるようなので、以下の入門動画を参考にしてみてください!
参考文献