システム開発11分で読めます

Next.js × Supabase Auth実装完全ガイド|Google認証とRLSセキュリティ設定

コセケン

コセケン

テクラル合同会社

#Supabase#認証機能#Next.js#Webアプリケーション開発#セキュリティ#バックエンド#BaaS#supabase auth
Next.js × Supabase Auth実装完全ガイド|Google認証とRLSセキュリティ設定

Next.jsで安全な認証機能を構築するなら、Supabase AuthとRLS(行レベルセキュリティ)の組み合わせが最適です。サーバーサイドでのセッション管理とデータベース層でのアクセス制御を統合することで、堅牢なシステムを短期間で実装できます。本記事では、Supabase Authの基本機能から、Next.js App Routerでの実装手順、Google認証の組み込み、middleware.tsによるセッション更新、そして本番運用に不可欠なRLSセキュリティ設定までを具体的に解説します。

Supabase Authの基本機能と多様な認証方法

Supabase Auth(正式名称:Supabase Authentication)は、Next.jsなどのモダンなフレームワークを用いたアプリケーションにおいて、ユーザー認証と認可を迅速かつ簡単に実装できる強力な機能です。内部的にはGo言語で書かれた専用の認証サーバーとして稼働しており、JWT(JSON Web Token)の発行やユーザー管理を効率的に処理します。

Supabase Authの基本機能

多彩な認証方法による柔軟なユーザー体験

Supabase Authを採用する際の重要な判断ポイントの一つは、多様なサインイン手法を標準でサポートしている点です。従来は個別に実装する必要があったパスワード認証をはじめ、マジックリンク、ワンタイムパスワード(OTP)、エンタープライズ向けのシングルサインオン(SSO)などを容易に組み込むことができます。

さらに、主要な外部プロバイダを利用したソーシャルログインにも対応しています。Supabase Authenticationを活用することで、ユーザーの離脱を防ぐスムーズなログイン体験を実現し、プロダクトの利用開始ハードルを大きく下げることが可能です。

PostgreSQLとの統合による完全なデータ制御

もう一つの基本事項として押さえておくべきなのが、データベースとの密接な連携です。SupabaseはPostgreSQLをベースとしており、ユーザーの認証レコードはデータベース内の独立した auth スキーマに保存されます。

外部の認証プロバイダを利用する場合、ユーザーデータがブラックボックス化してしまい、独自のデータ分析やバックアップが困難になる課題がよく発生します。しかし、Supabase Authでは認証レコードを開発者自身が完全に制御できます。APIと認証の両方で、使い慣れた強力なリレーショナルデータベースの恩恵をそのまま享受できる点は、システム設計上の大きなメリットです。

Supabase AuthとNext.jsの実装手順|App RouterでのSSR対応

モダンなWebアプリケーション開発において、フロントエンドフレームワークとの親和性は重要な判断ポイントになります。特にSupabase AuthをNext.jsプロジェクトに組み込む場合、レンダリング手法に合わせた適切なパッケージの選定が不可欠です。

Next.js App Routerでの実装

@supabase/ssrパッケージの活用と実装例

Next.js App Router環境で認証を設定する際、公式では @supabase/ssr パッケージの利用が推奨されています。Next.jsは、ビルド時のプリレンダリング(SSG)、リクエスト時のサーバーサイドレンダリング(SSR)、APIルートを提供する非常に多機能なフレームワークです。

このパッケージを利用することで、サーバーコンポーネントやサーバーアクション内でも安全にセッション情報を取得できます。以下は、サーバーサイドでSupabaseクライアントを初期化し、ユーザー情報を取得する基本的なコード例です。

// utils/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // サーバーコンポーネントから呼び出された場合はエラーを無視する
          }
        },
      },
    }
  )
}

middleware.tsによるセッション自動更新

Next.js App RouterでSupabase Authを正しく動作させるには、middleware.ts でセッションを更新する処理が必須です。Supabase AuthのJWTは有効期限があるため、リクエストのたびにmiddlewareでトークンを更新しなければ、ログイン済みユーザーが突然セッション切れになる問題が発生します。

// middleware.ts(プロジェクトルートに配置)
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            request.cookies.set(name, value)
          )
          supabaseResponse = NextResponse.next({ request })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // セッションを更新する(この行は必須)
  const { data: { user } } = await supabase.auth.getUser()

  // 未認証ユーザーを /login にリダイレクトする例
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    const url = request.nextUrl.clone()
    url.pathname = '/login'
    return NextResponse.redirect(url)
  }

  return supabaseResponse
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}

ポイント: getSession() ではなく getUser() を使うこと。getSession() はサーバーサイドでは信頼できないため、Supabase公式は getUser() を推奨しています(2024年以降の公式ドキュメント準拠)。

Supabase AuthでのGoogle認証の具体的な設定手順

ソーシャルログインの中でも特に需要が高いのが、Googleアカウントを用いた認証です。実際のプロジェクトにおいて、Supabase AuthでGoogle認証を導入するには、大きく分けて2つのプラットフォームでの設定が必要になります。

Google認証の設定

GCPとSupabaseでの連携設定

まず、Google Cloud Platform(GCP)にアクセスし、必要に応じて新しいプロジェクトを作成します。その後、APIとサービスのセクションから「OAuthクライアントID」と「クライアントシークレット」を発行します。

承認済みのリダイレクトURIには、以下の形式でSupabaseのコールバックURLを登録します。

https://<your-project-ref>.supabase.co/auth/v1/callback

次に、取得したクライアントIDとシークレットをSupabaseのダッシュボード(Authentication > Providers > Google)に入力し、有効化します。

クライアントサイドでのGoogleログイン実装例

事前設定が完了したら、Next.jsのクライアントコンポーネントからGoogle認証を呼び出します。

// components/GoogleSignInButton.tsx
'use client'

import { createBrowserClient } from '@supabase/ssr'

export default function GoogleSignInButton() {
  const supabase = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )

  const handleSignInWithGoogle = async () => {
    await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: `${location.origin}/auth/callback`,
      },
    })
  }

  return (
    <button onClick={handleSignInWithGoogle}>
      Googleでログイン
    </button>
  )
}

/auth/callbackのRoute Handler実装

Google認証のOAuthフローでは、SupabaseがユーザーをアプリケーションのコールバックURLへリダイレクトします。このエンドポイントが存在しないと認証が完了しないため、必ず以下のRoute Handlerを作成してください。

// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/'

  if (code) {
    const cookieStore = await cookies()
    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return cookieStore.getAll()
          },
          setAll(cookiesToSet) {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          },
        },
      }
    )

    const { error } = await supabase.auth.exchangeCodeForSession(code)
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  // エラー時はエラーページへ
  return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}

このRoute Handlerが code パラメーターをセッションに変換し、middleware.ts がその後のリクエストで認証状態を維持します。Next.js App Routerの構成や仕組みについてはこちらで詳しく解説しています。

RLSによるデータベースセキュリティの強化

認証機能によって「ユーザーが誰であるか」を特定した後は、「そのユーザーがどのデータにアクセスできるか」を制御する認可の仕組みが不可欠です。Supabase Authは認証にJWTを使用しており、このJWTはデータベースの機能と統合されているため、行レベルセキュリティ(RLS)を利用した高度なアクセス制御を簡単に実現できます。

RLSによるセキュリティ強化

すべてのテーブルでRLSを有効化する

RLSは、データベースレベルでユーザーがアクセスできる行を直接制御する仕組みです。Supabaseではクライアントサイドからデータベースへ直接クエリを発行できる強力な機能が提供されていますが、RLSが設定されていないと、誰もがすべてのデータを読み取れてしまうという重大なセキュリティリスクが生じます。

そのため、データ漏洩を防ぐための基本事項として、必ずすべてのテーブルでRLSを有効にすることが推奨されています。

RLSポリシーの具体的な設定例

RLSを有効にした後は、具体的なアクセスルールを定義する「RLSポリシー」を作成します。RLSポリシーは、PostgreSQLがアプリケーションからのクエリに対して自動的に適用する、暗黙的なWHERE句やCHECK制約のように機能します。

USING 句を用いることで、SELECT・UPDATE・DELETEの操作において、その行が特定のユーザーに可視化されるかどうかを決定できます。以下は、ユーザーが自分のプロフィールデータのみを読み書きできるようにする具体的なSQL設定例です。

-- テーブルのRLSを有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- 自分のプロフィールのみ参照可能にするポリシー
CREATE POLICY "Users can view own profile" 
ON profiles FOR SELECT 
USING ( auth.uid() = user_id );

-- 自分のプロフィールのみ更新可能にするポリシー
CREATE POLICY "Users can update own profile" 
ON profiles FOR UPDATE 
USING ( auth.uid() = user_id );

auth.uid() 関数を用いて現在ログインしているユーザーのIDとレコードの所有者IDを比較することで、「自分のデータしか読み書きできない」という強固な壁を築くことができます。ローカル開発環境でのマイグレーション管理には Supabase CLIの活用 が効率的です。

APIキー管理とセキュリティのベストプラクティス

プロダクトを本番環境で運用する上で、認証基盤のセキュリティ強化と権限管理は極めて重要なテーマです。APIキーの適切な管理方法と、パスワードの安全性確保に関する基本事項を整理します。

APIキー管理

サーバーサイドでの安全なキー管理

Next.jsを用いた開発において、APIキーやシークレットの管理には細心の注意が必要です。特に service_role キーは、設定されたRLSを完全にバイパスし、データベースへのフルアクセスを許可してしまう非常に強力な権限を持っています。

そのため、これらのキーは絶対にクライアントサイドのコードに含めてはいけません。必ずサーバーサイドでのみ保持し、ソースコードに直接書き込むのではなく、環境変数として安全に保存する必要があります。誤ってフロントエンドに公開してしまうと、悪意のあるユーザーによってデータベースの情報を自由に操作される危険性があります。

漏洩パスワードのブロック機能

さらに高度なセキュリティ対策として、ユーザーアカウントを保護する機能も提供されています。Supabaseは、過去にデータ侵害で漏洩したパスワードの使用を自動的に拒否する機能を提供しています。

この機能はProプラン以上で利用可能であり、ユーザーが脆弱なパスワードや使い回しのパスワードを設定してしまうリスクを未然に防ぐことができます。特に機密情報を扱うアプリケーションでは、導入を検討すべき重要な判断ポイントです。

よくある質問(FAQ)

Supabase Authの無料枠はどこまで使えますか?

SupabaseのFreeプランでは、月間50,000人までのアクティブユーザー(MAU)に対応しています。MVP開発や立ち上げフェーズのサービスであれば、コストをかけずに十分な認証基盤を構築可能です。

middleware.tsを書かないとどうなりますか?

ログインは一度成功しても、JWTの有効期限(デフォルト1時間)が切れた後のリクエストでセッションが更新されず、サーバーコンポーネントで getUser() を呼び出すとnullが返ります。結果として、ログイン済みユーザーが突然ログアウト扱いになる不具合が発生します。middleware.ts はセッション維持に必須です。

getSession()とgetUser()の違いは何ですか?

getSession() はローカルのCookieからセッションを読み取るだけでサーバーサイド検証を行いません。一方、getUser() はSupabaseサーバーへの問い合わせを行い、JWTの有効性を確認します。セキュリティが求められるサーバーサイドコードでは必ず getUser() を使ってください(2024年以降の公式推奨)。

Next.jsのPages Routerでも利用可能ですか?

はい、利用可能です。ただし、公式が推奨する @supabase/ssr パッケージはApp Router環境向けに最適化されています。Pages Routerを使用する場合は、環境に合わせてセッション管理の実装方法を調整する必要があります。

RLSを設定しないとどうなりますか?

RLSが無効な状態だと、APIのURLとパブリックキーを知っているすべてのユーザーがデータベースの全データを読み書きできてしまいます。これは重大な情報漏洩リスクとなるため、必ずすべてのテーブルで有効化し、適切なアクセス制御のポリシーを設定してください。

まとめ

Supabase Authは、Next.jsを用いたモダンなWebアプリケーション開発において、認証機能の迅速な実装と強固なセキュリティを両立させる強力なツールです。本記事を通じて、以下の重要なポイントを解説しました。

  • @supabase/ssrパッケージ: createServerClient / createBrowserClient でSSR・CSRの両方に対応したクライアントを生成できます。
  • middleware.ts: リクエストごとにJWTを更新する必須の処理。getUser() を使ってサーバーサイドでセッションを安全に検証します。
  • Google認証: GCPでOAuthクライアントを発行しSupabaseに登録。/auth/callback のRoute Handlerで認可コードをセッションに変換します。
  • RLS: すべてのテーブルで有効化し、auth.uid() を使ったポリシーで「自分のデータのみ操作可能」な安全な設計を実現します。
  • セキュリティベストプラクティス: service_role キーはサーバーサイド専用、漏洩パスワードブロック機能はProプランで利用可能です。

これらの知見を活かし、Supabase Authを最大限に活用することで、開発者はセキュリティと利便性を兼ね備えたWebアプリケーションを効率的に構築できるでしょう。

この記事を書いた人

コセケン

コセケン

テクラル合同会社

スタートアップでのCTO経験を経て、現在はテクラル合同会社にてシステム開発全般を牽引しています。アプリおよびWebの開発から、バックエンド、インフラ構築に至るまで幅広い技術領域に対応可能です。スピード感を持った品質の高いシステム開発を得意としており、新規プロダクトの立ち上げを一気通貫で支援します。本ブログでは実践的な開発ノウハウを発信していきます。

関連記事