【Next.js 15 ☓ Clerk】もう悩まない！認証機能を5分で開発する方法

# はじめに
今回ですが、Next.js 15とClerkという認証系SaaSを使用して、5分で認証機能を開発していこうと思います。
ハンズオンでも進められる内容になっているので、試してみたい方は事前にClerkにて、アカウント登録を行っておいてください。

https://clerk.com/

今回のソースですが、以下から閲覧可能です。

https://github.com/sc30gsw/next15-auth-with-clerk

# Clerkとは
Clerkは認証系の機能を提供してくれる認証系のSaaSです。
認証に必要なものは、ほぼすべて提供してくるため、認証の実装を簡単に記述できたり、Clerk内で、認証ユーザーの管理を行えたりと、面倒かつ煩雑な認証機能を丸投げすることができます。

私が使用してみて感じたメリットは以下です。

- 認証用画面をコンポーネントで提供してくるため、認証画面はコピペで作成できる（細かいスタイリングは必要）
- ドキュメントに沿うだけで認証機能作れる（ほぼコピペでできます）
- OAuth(GitHubやGoogle認証など)はClerk側で自動で行ってくれる
  - 自分でGCPやGitHubのsercret keyやcallback_uriの設定や発行が不要
- ユーザー情報の管理機能まで提供される（Clerkダッシュボード（管理画面）でもできますし、ユーザー自身が行うこともできます）
- email・passwordの二段階認証のUIまである
    - メール送信ももちろん行われる
- 公式ドキュメントにはAI検索機能が搭載（これからはこういう公式ドキュメントが増えそう）

上記を自力で実装しようとすると、かなり大変だと思いますが、Clerkを使えば、これらの複雑な認証機能を5分で作れます。

# アプリケーションのセットアップ
以下コマンドを実行します。
私はBunを使っていこうと思います。

https://bun.sh/

```sh
bunx  create-next-app@latest --typescript --tailwind
```

各オプションはデフォルトでよいですが、私の場合はESLintを使わずbiomeを使うため、そこだけNoを選択しました。

https://biomejs.dev/ja/


# Clerkのセットアップ
まず、Clerk上でapplicationの作成を行っていきます。
使用したい`Sign in options`にをONにするだけで、OAuthの認証機能を使えます。

デフォルトでEmail・Googleの認証がONになっています。
今回、私はGitHubの認証もONにして使っていこうと思います。
![clerc-setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/efacdf87-8acb-7eeb-793c-8090163be772.png)

こちらの設定が終わったら、`Create application`をクリックします。
すると、以下の画面に遷移するため、手順に従っていきます。

![setup-turn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/bceab675-683b-0aa6-d669-79eb6590cd4d.png)

### ライブラリ追加
画面の手順に表示されるため、コピペでできます。
```sh
bun add @clerk/nextjs
```

### 環境変数設定
画面の手順に表示されるため、コピペでできます。
```txt:.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=********************************
CLERK_SECRET_KEY=********************************
```

### middlewareの作成
`src`ディレクトリを使用している場合は`src`配下に`middleware.ts`を作成します。
`src`ディレクトリを使用していない場合は、rootディレクトリ配下に`middleware.ts`を作成します。

この手順も画面に表示されるため、コピペでできます。
```ts: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を使って検証を行うようです。


https://zenn.dev/maiamitorio/articles/cf893ff7827b19


### ClerkProviderの追加
`app/layout.tsx`にて、`ClerkProvider`を追加していきます。
こちらも画面の手順に表示されるため、コピペでできます。
```tsx:layout.tsx
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で認証機能を実装
ここから本格的に、認証機能を実装していきます。
以下を参考にして行っていきます。

https://clerk.com/docs/references/nextjs/custom-signup-signin-pages?_gl=1*1rxkf3r*_ga*Mjg1MTU2MTI5LjE3MzI3Njg0OTA.*_ga_1WMF5X234K*MTczMzI4ODgxMC41LjEuMTczMzI4OTM4MC4wLjAuMA

### sign-upページの作成
`app/sign-up/[[...sign-up]]/page.tsx`を作成しています。
こちらは`Optional Catch-all Segments`というsegmentで、`/sign-up`以下のパスパラメータを何でも受け取るというようなsegmentになります。

https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments

以下のようにします。
`fallbackRedirectUrl`に認証後にリダイレクトさせたいパスを渡します。

また、後ほど、RootLayoutの修正を行うめ、少しスタイリングを調整します。
```tsx:page.tsx
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`を作成し、同じようにコンポーネントを作成します。

```tsx: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ページにリダイレクトさせる処理です
```ts:middleware.ts
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)(.*)',
  ],
}
```

https://clerk.com/docs/references/nextjs/clerk-middleware

### 環境変数を追加
ドキュメントに従い、以下の環境変数を追加していきます。
```txt:.env.local
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
```

### RootLyaoutの修正
以下のドキュメントを参照し、Clerkが提供するコンポーネントを組み込み、ヘッダーを作成していきます。
また、スタイルを少し調整します。

https://clerk.com/docs/quickstarts/nextjs#add-clerk-provider-and-clerk-components-to-your-app

```tsx:layout.tsx
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もトレンドをおさえているので非常に、好感が持てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/29f78a5d-35e9-d96f-f6ab-b2ee2078f7d5.png)

#### email・password認証
機能もしっかりしており、アカウントにないメールアドレス入力で弾かれるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/6149f55d-ad9a-5c96-7d02-fdadfce80585.png)

また、パスワードリセットもデフォルトであるようです。
試しに、リセットしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/fc39f0f5-79a4-b2ed-837d-264bc0cb78b4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/61a121a6-84e0-7cce-3b20-328f1eab1dff.png)

二段階認証も付いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/13a6cb58-7cb2-60a3-1077-0ba19d11add3.png)

コード再送信機能も当然のようについています！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/410e6c67-686c-0e0d-4d8b-7b56590203cf.png)

また、この二段階認証のUIですが、自前で実装しようとすると、かなり大変です。
なので、コンポーネントライブラリに任せるのが一般的ですが、それでも大変です。
ただ、Clerkの場合、デフォルトでUXもしっかりしており、テンキーで動かせるので、そこもポイント高いです。
（この間、テンキーどころかカーソル当てても該当のinput要素にフォーカスされないシステムが有り、かなり悪い印象を植え付けられました）


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/85befa5c-fcab-d470-8b79-e55a21c0317c.png)

二段階認証のコードについては、入力したメールアドレス宛に送られてきます。
![send-email.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/509fa7ae-b781-73c6-8582-619de5b53f09.png)

パスワード再設定画面も完璧ですね！
入力すると、ログインされるみたいで、トップページに遷移します。
この辺のリダイレクトは要件にもよるので、ログインページに再度飛ばすこともできるとは思います。
（ドキュメントで調べてみてください）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/88976de7-13d1-a8b5-24bf-ca0229260a2c.png)


### sign-upページ
こちらもほぼ、sign-inページと同じですね。
こちらも試しにemail・passwordの認証を行おうと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/449d7524-0dd9-9aae-d14e-b106db4d4471.png)

#### email・password認証
まずは、パスワードのチェックから見ていきましょう。
入力を行い、Submitすると、弾かれました。
文字数だけでなく、パスワードのセキュリティチェックも行ってくれるみたいです。
（これも自力でバリデーションしようとすると結構、面倒です。）
![secure-password.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/dbdadd05-b01d-dfcf-4fb3-68d200d03fdb.png)

セキュアなパスワードを入力するとUIがセキュリティ条件をクリアしたことを知らせてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/2f423a1a-4b84-448b-1a52-d53ae217f8ed.png)


パスワードを入力できたら、二段階認証の画面に遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/dccb3538-3796-2a75-a53c-ec8818d4fe33.png)

あとは、sign-inと同じ流れで、二段階認証の再送もできますし、二段階認証通れば、トップページにリダイレクトされます。


### ユーザー情報管理機能の実装
ここでは認証済みユーザーに関連する処理を見ていきます。
まずは、ログインユーザー取得機能を追加していきます！

#### Server Component
以下を参照し、Server Componentからユーザーを取得してみましょう。
※ 後ほど、Client Componentからも取得する方法を記載するため、Client Componentを作成しています。

https://clerk.com/docs/references/nextjs/read-session-data

```tsx
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の両者の方法を紹介するため、今回はどちらも行っています）

https://clerk.com/docs/references/nextjs/read-session-data#client-side

以下のようにしますが、基本的には、`useUser()`のみ使用すればよいと思います。
私は、userIdを表示するために、`useAuth()`も使っています。

```tsx:page.tsx
'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のように表示されます。

![clerc-account-info.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/f6d7e7d8-954a-a1e2-46e0-256ac26489a6.png)

#### ユーザーボタン
ヘッダーのユーザーボタンをクリックすると以下のようなUIが表示されます。
これもClerk側が用意しており、こちらでは何も実装していないです。

![clerc-user-button.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/f0cf247c-946d-7b23-928b-7ed87e94e6fd.png)

それでは、`Manage account`をクリックしてみましょう！
以下のようにユーザー編集UIが表示されました。

![clerc-account-manage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/1d22cee3-7090-073b-7912-32f3393dd400.png)

GitHub認証の場合は以下のようになります。
![clerc-account-manage-with-github.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/e46d839c-1599-c38e-9c92-1faaf49912b1.png)




これで今回実装した、認証の内容は以上です。

最後にClerkのダッシュボードを見てみましょう！
以下のように管理画面でユーザー一覧が表示され、更新・削除などの管理機能を使えます。
![clerk-manage-user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/6690bce8-7bca-79b5-00ee-7186cc8c064e.png)


# おわりに
認証系SaaSのClerkどうでしょうか？
他にも認証系のことはほぼ網羅的に機能として提供されているので、かなりおすすめです！

料金プランもProプランで月額25ドルなので、これほどまでの認証系の機能を提供してくれているので、かなりお手軽かなと思います。

https://clerk.com/pricing

また、WebHookなどの設定もできるようなので、以下の入門動画を参考にしてみてください！

https://www.youtube.com/watch?v=9xNczyCRRKs

# 参考文献

https://clerk.com/

https://clerk.com/docs/references/nextjs/custom-signup-signin-pages?_gl=1*1rxkf3r*_ga*Mjg1MTU2MTI5LjE3MzI3Njg0OTA.*_ga_1WMF5X234K*MTczMzI4ODgxMC41LjEuMTczMzI4OTM4MC4wLjAuMA

https://clerk.com/docs/references/nextjs/clerk-middleware

https://zenn.dev/maiamitorio/articles/cf893ff7827b19

https://clerk.com/docs/quickstarts/nextjs#add-clerk-provider-and-clerk-components-to-your-app

https://clerk.com/docs/references/nextjs/read-session-data

https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments

https://zenn.dev/hayato94087/articles/effd7e94ae8302

https://www.youtube.com/watch?v=9xNczyCRRKs

https://youtu.be/aQN4bJ1yc-k