0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ClerkのOAuthでユーザーの新規登録時にDBへデータを自動送信する方法

Posted at

この記事のポイント

  • Clerkのwebhookの使い方を学べる
  • Clerkのwebhookの検証方法がわかる
  • ngrokを使い、ローカル環境でも外部からアクセス可能なエンドポイントの作成方法がわかる

経緯

ClerkのOAuthを使用してユーザー認証を行っている場合、新規ユーザーの登録をWebアプリ側で検知するのが難しい。そのため、ClerkのWebhookを利用し、自動的にDBへユーザー情報を送る仕組みを構築しました。そのときの開発メモです。

前提

  • 今回は ユーザーのサインアップ時(User creation) に、ClerkのWebhookを使って、DBにユーザー情報を自動で送るようにするための手順を構築する想定で進めます。
  • 基本的にはClerkの公式ドキュメントの手順に沿って解説します。
  • DBはNeon(postgresql)を使用しています。

実装手順

1. ngrokのセットアップ

今回はClerkの公式ドキュメントにもあるようにngrokを使って、Webhookのリクエストをアプリが受け取れるようにします。

1-1. ngrokのインストールとセットアップ

ngrok公式ページにアクセスして、サインアップしてセットアップしてください。
※ログインするとSetup&Installationから手順を確認できます。

1-2. 外部公開URLを取得

ご自身の開発環境をnpm run devなどで立ち上げ、そのローカルホストと同じものをngrokで実行します。
▽例: http://localhost:3000 の場合

ngrok http 3000

以下の赤枠のURL(Forwarding)が確認できればOKです。
ngrok-url.png

補足:ngrokとは

ローカルで動作しているWebアプリを、一時的にインターネット上に公開できるツールです。今回で言うと、ClerkのWebhookからローカル環境へリクエストを送るために使用します。

2. ClerkのWebhookを設定する

2-1. Clerkのダッシュボード にアクセス

dashboard-button.png

2-2. Webhooks タブからAdd Endpointボタンをクリック

sidemenu-webhook.png
endpoint-button.png

2-3. POST で受け取るエンドポイントを設定

①Endpoint URL
Forwarding URL + エンドポイント(例:/api/webhooks/user)となるようにして登録。
例:https://fawn-two-nominally.ngrok-free.app/api/webhooks/user

②Description
エンドポイントに関する説明を必要であれば追加。
(例:新規ユーザーがサインアップした場合、DBに自動でユーザーデータを送信)

③Subscribe to events
どのイベント時にこのエンドポイントにPOSTリクエストを送るかを設定。
今回はユーザー新規登録時なので、Webhookのイベントとしてuser.createdを選択。

④上記の登録完了後、Createボタンをクリック
setup-endpoint.png

3..env.localファイルにSigning Secretを追加

Webhookのペイロードを検証するには、エンドポイントのSigning Secretが必要です。
作成したエンドポイントの詳細画面からSigning Secretを取得し、.env.localファイルに追加します。

.env.localファイルには、基本的に既にデータベースのURL情報やClerk API キーが含まれているはずなので、次のようになると思います。

▽.env.localファイル

DATABASE_URL=postgresql://database_url
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_YXdha2Utb3gtODUuY2xlcmsuYWNjb3VudHMuZGV2JA
CLERK_SECRET_KEY=sk_test_5DbmoNJeli74tMRgtzZLBY4SC6gpTLbNGnvAPeWJkE
SIGNING_SECRET=whsec_123

4. ミドルウェアでwebhookのルートがパブリックになるように設定

clerkMiddleware()を使用している場合は、/api/webhooks(.*)ルートがパブリックに設定されていることを確認してください。ルートの設定については clerkMiddleware() のガイドを参照してください。

▽参考:以下のような設定であればapiがパブリックで実行されるはずです

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)(.*)',
  ],
}

5. svixをインストール

Clerkはsvixを使ってWebhookの署名付きリクエストを送っているので、サーバー側で不正なリクエストでないか検証するために使います。ターミナルで以下のコマンドを実行し、パッケージをインストールしてください。

npm install svix

6. Next.js API Route を作成する

2で設定したエンドポイントに合わせて、ルートファイルを作成し、Webhookリクエストを受け取れるようにする。
▽例:/src/app/api/webhooks/user/route.ts

import { NextResponse } from 'next/server';
import { Pool } from 'pg';
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';

// Neon DB へのコネクションを設定
const pool = new Pool({
  connectionString: process.env.DATABASE_URL, // Add to .env
});

// POSTリクエスト
export async function POST(req: Request) {
  const SIGNING_SECRET = process.env.SIGNING_SECRET;

  if (!SIGNING_SECRET) {
    throw new Error('Error: Please add SIGNING_SECRET from Clerk Dashboard to .env or .env.local');
  }

  // Create new Svix instance with secret
  const wh = new Webhook(SIGNING_SECRET);

  // Get headers
  const headerPayload = await headers();
  const svix_id = headerPayload.get('svix-id');
  const svix_timestamp = headerPayload.get('svix-timestamp');
  const svix_signature = headerPayload.get('svix-signature');

  // If there are no headers, error out
  if (!svix_id || !svix_timestamp || !svix_signature) {
    return new Response('Error: Missing Svix headers', {
      status: 400,
    });
  }

  // Get body
  const payload = await req.json();
  const body = JSON.stringify(payload);

  let evt: WebhookEvent;

  // Verify payload with headers
  try {
    evt = wh.verify(body, {
      'svix-id': svix_id,
      'svix-timestamp': svix_timestamp,
      'svix-signature': svix_signature,
    }) as WebhookEvent;
  } catch (err) {
    console.error('Error: Could not verify webhook:', err);
    return new Response('Error: Verification error', {
      status: 400,
    });
  }

  // `user.created` イベントの処理(適宜変更してください)
  try {
    if (evt.type === 'user.created') {
      const { id, email_addresses } = evt.data;
      const email = email_addresses?.[0]?.email_address || "";

      // DBにユーザーを登録
      await pool.query(
        "INSERT INTO users (id, email) VALUES ($1, $2) ON CONFLICT (id) DO NOTHING",
        [id, email]
      );
      console.log(`User ${id} added to database.`);
      return NextResponse.json({ message: 'User saved to DB' }, { status: 200 });
    }

    return NextResponse.json({ message: 'Unhandled event' }, { status: 200 });
  } catch (error) {
    console.error('Webhook Error:', error);
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

7.Webhookの検証

  1. 全ての設定が完了したらエンドポイントの設定ページから「Testing」タブを選択
  2. Send eventのプルダウンからuser.createdを選択
    testing.png
  3. ページ下部のSend Exampleを選択
  4. Message Attemptsのセクションで実行したイベントが以下のようにSucceededと表示されていることができれば成功です!🎉
    message.png

これで、ClerkのOAuthを使ったユーザー認証時でも、新規ユーザー情報をDBへ自動登録できるようになります!
実際にアプリ内でユーザーを新規登録してみて正しく実行されるか確認してください。

デバッグ

テストでエラーが発生した場合に、主に確認すべき点を挙げます。

  • ミドルウェアの設定を確認(apiがパブリックで実行されるようになっているか)
  • Clerkの設定画面で正しいエンドポイントが設定されているか
    URL+/api/webhooks/指定のルートとなっているか確認
    例:ルートファイル)/src/app /api/webhooks/user/route.ts
      エンドポイント)http://〜 /api/webhooks/user
  • ルートハンドラやAPIルートの設定が正しいか確認
    Webhookに問題がある場合、基本的なルートハンドラとレスポンスを作成し、ローカルでテストすることができます。

①アプリ内にテストのルートを作成し、以下のコードを記述
test-route.png

app/webhooks/test/route.ts

export async function POST() {
  return Response.json({ message: 'The route is working' })
}

②アプリを実行
③以下のコマンドを実行

curl -H 'Content-Type: application/json' \
    -X POST http://localhost:3000/api/webhooks/test

{"message":"The route is working"}が表示されたら、基本的なルートハンドラは動作しており、ビルドする準備ができています。

補足:Clerk上でユーザー情報を削除したい場合

Userタブから削除できます
user-tab.png

デプロイする場合

基本的に手順は同じです。
①Clerkでデプロイ先のURL+エンドポイント(/api/webhooks/*)を追加
Signing Secretをデプロイ先のEnvironment Variablesに設定
③デプロイ
これでローカル環境と同様に実行できるはずです。

まとめ

  • Clerk のWebhookを使うと、新規ユーザー登録をリアルタイムで検知できる。
  • ローカル開発時はngrokを活用すると、外部からアクセス可能なエンドポイントを作れる。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?