🔥

Clerk × Hono で実現するシンプルで安全なバックエンド認証

に公開

第1回では、Next.jsとClerkを使用して、フロントエンド認証を実装しました。しかし、実際のアプリケーションではバックエンドAPIとの連携が不可欠です。バックエンドはデータの取得元や更新者であることが多いため、UI制御がメインのフロントエンドに比べ、より強固で網羅的なセキュリティ機能を実装する必要があります。

しかし、バックエンドでの認証実装は非常に骨が折れる作業でありながら、個人レベルで実装するには非常に懸念のある機能となるでしょう。

そこで本記事では、引き続きClerkを使用し、素早く簡単に安全なセキュリティ機能を実装します。また、バックエンドにはHonoを使用し、直感的な開発とフロントエンドからの型安全な呼び出し方法を共有します。

前回の記事を読んでいなくとも理解できるような内容にしていますが、先に前回の記事を参照することで、より読み進めやすくハンズオンもしやすいものとなっています。


本記事では、Hono と Clerk を使用したバックエンド認証を取り扱います。

  1. Clerk で爆速認証実装 - Next.js アプリに 10 分で認証機能を追加する
  2. Clerk × Hono で実現するシンプルで安全なバックエンド認証(今ここ)
  3. Webhook による Clerk とアプリケーションデータの同期

実行環境

  • OS: Ubuntu 22.04.5 LTS(WSL2)
  • Node.js: v22.16.0
  • pnpm: 10.12.2
  • Next.js: 15.3.5
  • React: ^19.0.0
  • @clerk/nextjs: ^6.27.1
  • @clerk/backend: ^2.10.1
  • @hono/clerk-auth: ^3.0.3

なぜ Hono なのか

バックエンドAPIフレームワークには、Express.js、Fastifyや、Next.jsでサポートされているAPI Routesなど、多くの選択肢があります。その中で、今回Honoを選定した理由は以下の通りです。

Next.jsとの親和性

Honoのvercelアダプタを使用することで、Next.js API Routesとしても動作するため、既存のNext.jsプロジェクトに簡単に組み込むことができます。

Hono RPCによる型安全なAPI通信

Hono RPCを使用することで、フロントエンドからバックエンドへの通信を型安全に行えます。APIの型定義をフロントエンドに共有することで、コンパイル時にパス、リクエストボディ、パラメータの型などを補完、チェックできます。

ちなみに、今回使用するClerkもAPIサーバーでHonoを採用しており、実績のあるフレームワークと言えるでしょう。

バックエンド API の実装

Next.js の App Router では、API Routes を使用してバックエンド API を実装できます。今回は Hono フレームワークを使用して API を構築し、Clerk で認証を行います。

必要なライブラリのインストール

Hono 本体と、バックエンド用の Clerk 、 Hono で Clerk を使用するためのライブラリをインストールします。

pnpm add hono @clerk/backend @hono/clerk-auth

環境変数の設定

バックエンドでClerkの認証をするためには、Next.jsのフロントエンド認証と同様にClerkのAPIキーが必要になります。Honoでは、ローカル環境での環境変数は.dev.varsから取得します。そのため、バックエンドで必要になる環境変数は.dev.varsにコピペしてください。今回はバックエンドでどちらも使用します。

.dev.vars
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxx...
CLERK_SECRET_KEY=sk_test_xxxxxxxxxx...

環境変数を設定したら、これらを型安全に使用するために以下のコマンドを実行します。

pnpm cf-typegen

このコマンドを実行することにより、dev.vars内で定義した環境変数の型定義ファイル(coudflare-env.d.ts)がルートに作成され、補完やチェックができるようになります。

cloudflare-env.d.ts
declare namespace Cloudflare {
 interface Env {
  NEXTJS_ENV: string;
  NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: string;
  CLERK_SECRET_KEY: string;
  ASSETS: Fetcher;
 }
}
interface CloudflareEnv extends Cloudflare.Env {}

APIルートの作成

src/app/api/[[...routes]]/route.tsを作成します。

ここで、[[...routes]]という、見慣れない記法が出てきました。これは、Next.jsのOptional Catch-all Segmentsと呼ばれるものです。
このルートでは、/api/api/hello/api/clerk/webhookのように、/api以下の、すべてのパスへのリクエストをキャッチできます。
これにより、単一のエントリーポイントを作成することができ、フロントエンドから型情報を使いやすくなります。(フロントエンドからの型安全な API 呼び出しで後述します。)

src/app/api/[[...routes]]/route.ts
import { Hono } from "hono"

const app = new Hono()
  .basePath("/api")
// 以下にメソッドチェーン形式でAPIルートを作成する

今回は、/api以下のすべてのパスへのリクエストをキャッチするため、ベースとなるパスを/apiに設定します。

認証ミドルウェアの適用

次に、HonoのuseメソッドとClerkミドルウェアを使用して、認証情報やClerkのクライアントを使用できるようにします。
use(ミドルウェア)を使用することで、特定のパスで共通的に行いたい前処理または後処理を書くことができます。
このミドルウェアを使用して、ユーザー情報の取得・認証を行います。

src/app/api/[[...routes]]/route.ts
// import元を間違えないよう注意!(@clerk/nextjs/serverではないことに気を付ける)
import { clerkMiddleware } from "@hono/clerk-auth"
import { getCloudflareContext } from "@opennextjs/cloudflare"
import { Hono } from "hono"
import { HTTPException } from "hono/http-exception"

const app = new Hono()
    .basePath("/api")
    // Clerkミドルウェアを適用
    .use(clerkMiddleware({
        secretKey: (await getCloudflareContext({async: true})).env.CLERK_SECRET_KEY,
        publishableKey: (await getCloudflareContext({async: true})).env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
    }))
    // clerkを使用してユーザーIDを取得、存在しなければ401エラーを返す
    .use((c,next) => {
        const clerkAuth = c.get("clerkAuth")
        const auth = clerkAuth()
        if(!auth || !auth.userId) throw new HTTPException(401, {
            message: "You are not logged in."
        })
        return next()
    })

ここで、OpenNext特有の環境変数の扱いに注意が必要です。
OpenNext環境では、通常の Node.js アプリケーションや Hono アプリケーションとは異なり、process.envc.envでは環境変数を取得できません。本来は、自動で環境変数を取得する機能があるclerkMiddlewareですが、この環境では明示的に取得、提供する必要があります。
そのため、getCloudflareContextを使用して環境変数を取得します。

エンドポイントの実装

ここまで、Honoのミドルウェアを使用して認証機能を実装してきました。最後に、アクセスできるエンドポイントを作成して、ブラウザを使用して実際にアクセスしてみます。

src/app/api/[[...routes]]/route.ts
import { clerkMiddleware } from "@hono/clerk-auth"
import { getCloudflareContext } from "@opennextjs/cloudflare"
import { Hono } from "hono"
import { HTTPException } from "hono/http-exception"
// import元を間違えないよう注意!(hono/vercelであることを確認する)
import { handle } from "hono/vercel"

const app = new Hono()
    .basePath("/api")
    .use(clerkMiddleware({
        secretKey: (await getCloudflareContext({async: true})).env.CLERK_SECRET_KEY,
        publishableKey: (await getCloudflareContext({async: true})).env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
    }))
    .use((c,next) => {
        const clerkAuth = c.get("clerkAuth")
        const auth = clerkAuth()
        if(!auth || !auth.userId) throw new HTTPException(401, {
            message: "You are not logged in."
        })
        return next()
    })
    // GETエンドポイントを実装
    // ClerkのSDK クライアントを使用し、IDからユーザー名を取得
    .get("/hello", async c => {
      const clerkClient = c.get("clerk")
      const clerkAuth = c.get("clerkAuth")
      const auth = clerkAuth()

      const user = await clerkClient.users.getUser(auth!.userId!)

      return c.text(`Hello ${user.username}!`, { status: 200 })
    })

// GETメソッドをexportし、呼び出せるようにする
export const GET = handle(app)

getメソッドを使用し、/api/helloエンドポイントを実装します。ここで、Next.jsの API Routes 上で使用できるようにするために重要な手順があります。それは、hono/vercelhandle関数を使用して定数GETを名前付きexportすることです。(定数名はアプリケーションで使用するメソッドによって追加・変更してください。)
そうすることで、 Hono のエンドポイントを Next.js API Routes として扱うことが可能になります。

ここまでで、APIエンドポイントの実装は完了です。
この実装では、以下の順序でミドルウェアとエンドポイントを配置しています:

  1. 認証ミドルウェア: clerkMiddleware()で Clerk 認証を有効化
  2. 認証チェック: ユーザーがログインしているかを確認
  3. 保護されたエンドポイント: 認証が必要な API エンドポイント

動作確認

動作確認をするために、Next.jsのAPIルートと、検証用画面のルートで用意する/publicをパブリックルートにします。

src/middleware.ts
// パブリックルートの定義
const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/api/(.*)', '/public'])

// 省略

開発サーバーを起動して、API が正常に動作するか確認します。

pnpm dev
  1. http://localhost:3000/sign-in にアクセスし、アプリケーションにログインします。

  2. ログインできたことを確認したら、 http://localhost:3000/api/hello にアクセスしてみましょう。現在はログインしているため、/api/helloにアクセスでき、Hello {username}!と表示されます。
    Hello {username}!と表示される
    ログイン時:Hello {username}!と表示される

  3. 次に、 http://localhost:3000/ のユーザーボタンからログアウトを実行します。

  4. ログアウトができたら、もう一度 http://localhost:3000/api/hello にアクセスしてみましょう。今度は、ミドルウェアでリクエストが拒否され、You are not logged in.と表示されます。
    You are not logged in.と表示される
    ログアウト時:You are not logged in.と表示される

フロントエンドからの型安全な API 呼び出し

Hono RPCを使用することで、フロントエンドの React コンポーネントから型安全に API を呼び出すことも可能です。 Hono のクライアントと TypeScript の型定義を活用することで、コンパイル時に型チェックが行われ、より安全な開発が可能になります。

Honoのクライアントを使用する

Honoを型安全に使用するには、Honoアプリの型をExportし、Honoから提供されている、hono/clienthcを使用します。

src/app/api/[[...routes]]/route.ts
const app = new Hono()
// 中略

// 一番下でHonoアプリの型をexport
export type AppType = typeof app

フロントエンドで型安全に呼び出すためには、hono/clientが提供しているhcと先ほど定義したAppTypeを使用します。

メインページ(src/app/public/page.tsx)を以下のように実装します:

src/app/public/page.tsx
'use client'

import { useState, useEffect } from 'react'
import { hc } from 'hono/client'
import type { AppType } from '../api/[[...routes]]/route'

export default function Home() {
  const [message, setMessage] = useState<string>("")

  // 型安全なAPIクライアントを作成
  const client = hc<AppType>("/")

  useEffect(() => {
    // 型安全なAPI呼び出し
    client.api.hello.$get()
      .then(async response => {
        const text = await response.text()
        setMessage(text)
      })
  }, [])

  return (
    <div className="p-4">
      <h1 className="text-2xl font-bold mb-4">Clerk + Hono 認証テスト</h1>
      <div className="p-4 border rounded">
        <p>APIからのレスポンス: <strong>{message}</strong></p>
      </div>
    </div>
  )
}

hcを使用することにより、client.{path}.{method}のようにパスを指定できるため、パスのタイプミスを防ぐことができます。また、リクエストやレスポンスも適切に型付けされているため、リクエストボディの付与を忘れたり、レスポンスに存在しない値を取得してしまうことも防止でき、非常に開発体験が素晴らしいです。

api/helloが​補完機能で​候補に​挙げられる​
api/helloが​補完機能で​候補に​挙げられる​

レスポンスの​型、​ステータス、​レスポンスの​フォーマットが​型付けされる​
レスポンスの​型、​ステータス、​レスポンスの​フォーマットが​型付けされる​

動作確認

http://localhost:3000/public にアクセスし、ログイン時とログアウト時での表示の違いを確認してみてください。
ログアウト時に一度/に戻されるため、再度/publicを打ち込んでみてください。

Hello {username}!と表示される
ログイン時:Hello {username}!と表示される

You are not logged in.と表示される
ログアウト時:You are not logged in.と表示される

まとめ

本記事では、HonoとClerkを使用してバックエンドAPI認証を実装する方法を紹介しました。従来のバックエンド認証実装では複雑なセキュリティ対策が必要でしたが、Clerkを使用することで、フロントエンドと同様、わずか数十行のコードで安全かつ機能的なAPI認証システムを構築できることができます。また、Hono RPCによる型安全なAPI通信や、Next.jsとの優れた親和性により、開発効率を大幅に向上させることができる点が大きなメリットです。

次回の記事では、WebhookによるClerkとアプリケーションデータの同期について解説し、Clerkのイベントによるアプリケーション連携についてお伝えします。

株式会社SCC - テクノロジーセンター

Discussion