第四章:データベース連携:Supabase AdapterのバインドとUser Profileデータベースの拡張

前三章の努力を経て、私たちは完璧に動作するOAuth 2.0デュアルプラットフォームログインシステムを手に入れました。ユーザーはGoogleまたはLineでログインでき、パスワードの暗号化を自分で処理する必要はまったくありません。

しかし今、私たちは大きな問題に直面しています:データはどこに保存されているのか? デフォルトでは、Auth.jsは**JWTモード(JSON Web Token)**を使用しています。これは、ユーザーがログインに成功すると、Auth.jsがユーザーの名前、メールアドレス、プロフィール画像のURLをすべて「暗号化」してユーザーのブラウザのCookieに詰め込むことを意味します。

これは簡単なブログには十分ですが、私たちが構築しているVibe TutorのようなSaaS知識有料プラットフォームでは、これは完全に使えません!

なぜSaaSには本当のデータベースが必要なのか?

以下のシナリオを考えてみてください:

  1. サイトに現在何人の会員がいるかをどうやって知るのか?(Cookieは各人のコンピュータに保存されているため、数えることができません)
  2. 小明が3,999円のコースを購入した場合、「支払い済み」をどう記録するのか?(支払い済み状態をJWTに書き込む場合、ハッカーがJWTのpayloadを改ざんすれば無料でコースを見ることができます)
  3. 小明が規約違反でアカウント停止になった場合、どうやって彼を強制ログアウトさせるのか?(JWTは発行されるとサーバー側で強制的に無効にできず、有効期限が切れるまで待つ必要があります)

したがって、私たちは強力なリレーショナルデータベースを導入し、Adapterアーキテクチャを通じて、Auth.jsがすべての登録とログインのデータを自動的にデータベースに書き込むようにする必要があります!

本章では、世界最強のServerlessデータベースである**Supabase(PostgreSQL)**を使用して実戦を行います。


🗄️ ステップ1:Supabaseデータベーススキーマの準備

Auth.jsがあなたのデータベースを理解できるようにするには、テーブル(Tables)を適当に作成せず、Auth.jsが公式に定義するスキーマ仕様を厳密に遵守する必要があります。

  1. Supabaseプロジェクトダッシュボードにログインします。
  2. 左側のSQL Editorをクリックし、新しいクエリを作成します。
  3. Auth.jsが公式に提供するPostgreSQLのテーブル作成SQLを貼り付けて実行します:

```sql -- コアとなるUserテーブルの作成(「人」の実体を保存) CREATE TABLE users ( id uuid NOT NULL DEFAULT uuid_generate_v4(), name VARCHAR(255), email VARCHAR(255), "emailVerified" TIMESTAMPTZ, image TEXT,

-- ここであなた独自のビジネス関連フィールドを大胆に拡張できます! membership_level VARCHAR(50) DEFAULT 'free', total_spent INT DEFAULT 0,

PRIMARY KEY (id) );

-- Accounts関連テーブルの作成(この人がどのサードパーティProviderと紐づいているかを保存) CREATE TABLE accounts ( id uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, type VARCHAR(255) NOT NULL, provider VARCHAR(255) NOT NULL, "providerAccountId" VARCHAR(255) NOT NULL, refresh_token TEXT, access_token TEXT, expires_at BIGINT, token_type TEXT, scope TEXT, id_token TEXT, session_state TEXT, PRIMARY KEY (id), -- 外部キーを設定し、Userが削除されると紐づくアカウントも一緒に削除される CONSTRAINT fk_user FOREIGN KEY ("userId") REFERENCES users(id) ON DELETE CASCADE );

-- Sessionsテーブルの作成(ユーザーが現在どのデバイスでログイン中かを保存) CREATE TABLE sessions ( id uuid NOT NULL DEFAULT uuid_generate_v4(), "sessionToken" VARCHAR(255) NOT NULL, "userId" uuid NOT NULL, expires TIMESTAMPTZ NOT NULL, PRIMARY KEY (id), CONSTRAINT fk_user FOREIGN KEY ("userId") REFERENCES users(id) ON DELETE CASCADE );

-- パフォーマンスのために必要なインデックス(Indexes)を作成 CREATE UNIQUE INDEX accounts_provider_provideraccountid_idx ON accounts (provider, "providerAccountId"); CREATE UNIQUE INDEX sessions_sessiontoken_idx ON sessions ("sessionToken"); CREATE UNIQUE INDEX users_email_idx ON users (email); ```

このSQL文は非常に価値があります! よく見ると、usersテーブルに2つのフィールドmembership_level(会員レベル)とtotal_spent(総消費額)を追加しています。これがDatabase Sessionアーキテクチャの最も魅力的な点です:ビジネスロジックに関連するデータフィールドを無限に拡張できます。


🔌 ステップ2:Supabase Adapterのインストール

プロジェクトのターミナルに戻り、Auth.jsがSupabase向けに特別に設計したAdapterパッケージとSupabaseの公式SDKをインストールします:

```bash npm install @auth/supabase-adapter @supabase/supabase-js ```

インストールが完了したら、.env.localにSupabaseの接続文字列が設定されていることを確認してください。 🚨 極めて重要なセキュリティ警告: Auth.jsはサーバー側で「密かに」ユーザーデータを書き込むため、SupabaseのすべてのRLSセキュリティ対策を迂回する必要があります。そのため、ここでは**Service Role Key(スーパー管理者キー)**を使用する必要があり、anon keyは絶対に使用できません。

```env

.env.localにこれら2つを追加

NEXT_PUBLIC_SUPABASE_URL="https://xxxxx.supabase.co"

注意!このキーにはNEXT_PUBLIC_プレフィックスを付けてはいけません。そうでないとフロントエンドに漏洩します!

SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUz...これは長いスーパーキー��す" ```


⚙️ ステップ3:auth.tsでAdapterエンジンを起動

今度は、src/auth.tsを大幅に書き換えます。元々存在していた漠然としたCookieモードから、ハードコアなDatabaseモードに強制的に切り替えます。

src/auth.tsを開きます:

```typescript import NextAuth from "next-auth" import GoogleProvider from "next-auth/providers/google" import LineProvider from "next-auth/providers/line" import { SupabaseAdapter } from "@auth/supabase-adapter"

export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.AUTH_GOOGLE_ID, clientSecret: process.env.AUTH_GOOGLE_SECRET, }), LineProvider({ clientId: process.env.AUTH_LINE_ID, clientSecret: process.env.AUTH_LINE_SECRET, }), ],

// 🚀 キー設定1:Adapterのマウント adapter: SupabaseAdapter({ url: process.env.NEXT_PUBLIC_SUPABASE_URL!, secret: process.env.SUPABASE_SERVICE_ROLE_KEY!, }),

// 🚀 キー設定2:Session戦略の強制切り替え session: { // Adapterを設定すると、デフォルトでは"database"になります。 // しかし、"database"と明記することでチームがアーキテクチャを理解しやすくなります。 strategy: "database", // ログイン後どれくらいでログアウトされるかを設定(ここでは30日間) maxAge: 30 * 24 * 60 * 60, },

// 🚀 キー設定3:Callbacks(インターセプター) callbacks: { // フロントエンドでauth()またはuseSession()を呼び出すと、このcallbackがトリガーされます // この機会にデータベース内の追加フィールド(membership_levelなど)をフロントエンドに渡せます! async session({ session, user }) { if (session.user) { // userオブジェクトはデータベースから直接selectされた実際のデータです! session.user.id = user.id; // 型定義を拡張している場合、以下のようにすることもできます: // session.user.membership_level = user.membership_level; } return session; } } }) ```


🎇 ステップ4:奇跡の瞬間を目撃する

すべての設定が完了しました。今こそ成果を収穫するときです。

  1. 開発サーバーが実行中であることを確認します(npm run dev)。
  2. http://localhost:3000/api/auth/signinにアクセスします。
  3. これまでログインしたことのない別のGoogleアカウントでログインします。
  4. ログインに成功したら、すぐにSupabaseのダッシュボードに移動し、Table Editorを開きます。

衝撃的な事実を目の当たりにしてください:

  • **users**テーブルを開く:先ほどログインした名前、メールアドレス、高解像度のプロフィール画像URLが表示されています!INSERT INTO usersというSQL文を1行も書いていません!
  • **accounts**テーブルを開く:このユーザーがgoogleでログインしたことを記録した関連データと、Googleが提供したaccess_tokenが完璧に保存されています。
  • **sessions**テーブルを開く:ランダムなsessionTokenと有効期限が表示されています。

これがアーキテクチャの美しさです。 この時点で、管理者としてSupabaseのバックエンドでsessionsテーブルのそのレコードを手動で削除すると、数秒後にはそのユーザーのブラウザ上の状態が即座に「未ログイン」に変わります。これが大規模な商業システムでDatabase Sessionが必須である理由です。あなたは絶対的なコントロールを手にしています!

次章では、この強力なAuth.jsを利用して、支払いをしていない「ただ乗り」をサーバー側でブロックし、完璧な商業レベルのセキュリティ防御網を構築する方法について、このコースの最高潮に達します!

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!