Site icon image World Hacker(ぎょうざブログ)

Think Different, Connect Dots for Line.

【Tips - Clerk】実例でわかるシリーズ

<前置き、挨拶>

みなさんどうも、こんばんみ~。ぎょうざです。

Webアプリ上などでユーザー認証機能を実装する際に便利なツール

Clerk(クラーク)を利用する際に、ぎょうざが躓いたところを備忘録として記録しておきます。

同じようなケースあるいは現象で困っている方の参考になれば幸いです。

【目次】

<Useful>

▶ 本番環境にむけて設定したこと(Google認証を用いた例)

※各自自身の環境に読みかえて下さい。

  • DNS管理先の設定

Clerk側でProductionインスタンスを作成済みで、かつ自分の場合はCloudflareのドメイン管理ですでに、「gyozaless.com」の独自ドメインを取得済みで、サブドメインとして「to-gyoza-er.gyozaless.com」をDNS設定した前提です。デフォルトで登録使用すると、プロキシステータスがリバーシプロキシを設定してしまい、Clerk側にDNS適用情報が伝達しないので、DNSのみになっているか確認して下さい

Image in a image block

  • Clerkダッシュボード

ConfigureのDevelopersのDomains画面で、上記のDNS設定がインターネット側で適用が終わると、Clerk側でも適用済みがダッシュボード上で確認できます。これで本番環境でClerkの認証画面が開くようになります。ただし、Google側のOAuth認証も設定してあげないと、Googleでのログイン認証がまだ未実装の状態です

Image in a image block
  • GoogleのOAuth認証情報の取得

Google Cloudコンソールから、APIとサービス▶認証情報で、APIキーを発行すると「Client ID」が「Client Secret」が発行してもらえます

  • 再び、Clerkダッシュボード

ConfigureのUser & AuthenticationSSO connections画面で、上記でGoogle Cloudで発行したAPIキー各項目を入力してUpdateします。

Image in a image block

以上で、Google認証を用いたClerkの本番環境設定が完了です。

最後に、Clerk(Productionインスタンス)側のOverview画面でログインユーザーを確認してみて下さい。

Image in a image block

<Error>

▶ userId (※ぎょうざ独自にPrismaのSchemaに定義) をSupabaseのDBにPOSTできない
  • 現象:TypeError: (0 , clerk_nextjs__WEBPACK_IMPORTED_MODULE_2_.useAuth) is not a function
  • 原因:Internal Error 500
Image in a image block
  • 解決策:import元をimport { useAuth } “@clerk/nextjs”からimport { auth } “@clerk/nextjs/server”へ変更

▶ npm run dev で 開発コンソール上でGET / 307が連続で表示される
  • 現象:ブラウザ表示画面上で、ERR_TOO_MANY_REDIRECTSがエラー表示される
  • 原因:Next.jsとClerkの認証設定が競合してリダイレクトが繰り返されている
    💡
    原因を推測するに、middleware.tsisPublicRouteconfig.matcher の設定が競合していた可能性があります。

    具体的には、

    1. localhost:3000 にアクセスすると、middleware.ts が実行されます。
    2. /isPublicRoute に含まれているため、userAuth.protect() は実行されません。
    3. しかし、config.matcher の設定により、localhost:3000 はミドルウェアの対象となります。
    4. その結果、Clerk のミドルウェアが何らかの処理を行い、意図しないリダイレクトが発生していた可能性があります。

    /isPublicRoute から削除することで、localhost:3000 へのアクセス時に userAuth.protect() が実行されるようになり、Clerk のサインインページへのリダイレクトが正しく行われるようになったと考えられます。

    Clerk のミドルウェアは、認証が必要なページにアクセスがあった際に、ユーザーをサインインページにリダイレクトする役割を担います。/isPublicRoute に含めていた場合、ルートページへのアクセスも認証が必要と判断され、リダイレクトが発生していた可能性があります。

    ただし、/isPublicRoute から削除することで、ルートページへのアクセスが常に認証を要求されるようになります。もしルートページを認証なしでアクセスできるようにしたい場合は、別の方法で対応する必要があります。

  • 解決策:isPublicRouteからルートページ、自分の場合は'/'を削除

▶ Clerk Auth & Middleware: 「検知できません!」エラーとの戦い

現象:
APIルート(ルートハンドラ)内で await auth() (from @clerk/nextjs/server) を呼び出すと、「Clerk: auth() was called but Clerk can't detect usage of clerkMiddleware()...」というエラーが発生する。

原因:
Clerkの auth() ヘルパーが正しく機能するためには、そのリクエストが事前に clerkMiddleware を通過し、認証コンテキストがセットアップされている必要がある。このエラーは、ミドルウェアが当該APIルートに対して実行されていない、または実行されてもコンテキストを適切に設定できていないことを示す。主な原因は middleware.tsconfig.matcher の設定漏れか、ミドルウェア内のロジック。

解決策:

  1. middleware.tsconfig.matcher が、エラーの発生するAPIルート(例: /(api|trpc)(.*))を確実に含んでいることを確認する。
    TypeScript
    // middleware.ts
    export const config = {
      matcher: [
        '/((?!.+\\.[\\w]+$|_next).*)', // 静的ファイル等を除外
        '/',                           // ルート
        '/(api|trpc)(.*)',             // APIルートを含める
      ],
    };
  2. middleware.tsclerkMiddleware コールバック関数内で、APIルートへの未認証アクセスに対しては、authObject.protect() でページリダイレクトを試みるのではなく、NextResponse.json({ error: '認証が必要です。' }, { status: 401 }); のように明示的に401エラーを返すようにする。これにより、auth.protect() がAPIルートに対して予期せず notFound() をトリガーするのを防ぐ。
    TypeScript
    // middleware.ts
    const isApiRoute = createRouteMatcher(['/api/(.*)']);
    export default clerkMiddleware((authObject, request) => {
      // ... (isPublicRouteのチェック) ...
      const { userId, protect } = authObject;
      if (!userId) { // 未認証の場合
        if (isApiRoute(request)) {
          return NextResponse.json({ error: '認証が必要です。' }, { status: 401 });
        }
        return protect(); // ページならリダイレクト
      }
      return NextResponse.next();
    });
    • Tips: ミドルウェア内の auth 引数(例ではauthObject)は、Clerkの認証情報を持つオブジェクトそのものであり、APIルート内の await auth() とは呼び出し方が異なる点に注意。

<Warn>

〆の一言

Clerkは、ユーザー認証機能を簡単に実装できるツールとして優秀です。
メール認証だけにとどまらず、Google認証やSNS認証なども可能です。

ここまで読んでいただき、ありがとうございました。

以上、ぎょうざでした。