📑

Docker環境でSupabase OAuth認証が失敗する問題と解決策【Next.js + Google OAuth】

に公開

はじめに

Docker環境でSupabaseのOAuth認証を導入する場合、公式のドキュメント通りに進めても様々な問題に遭遇します。

これは、Dockerコンテナ内とブラウザでSupabaseへのアクセス方法が異なるためです。

本記事では、このDocker環境特有の課題を解決する具体的な手順を、実装コードと共に詳しく解説します。同じ問題で困っている方の参考になれば幸いです。

事前準備

完全なサンプルコード
この記事で解説するすべてのコードはGitHubリポジトリで公開していますので、実際に試していただけます。

技術スタック

今回のプロジェクトでは以下の技術を使用しています。

  • Next.js 15 (App Router)
  • Supabase (ローカル環境)
  • Docker + Docker Compose
  • Google OAuth 2.0 (PKCE)
  • TypeScript

Next.jsでのSupabaseクライアント設定

公式ドキュメントにも明記されているように、Next.jsではサーバーサイドとクライアントサイドで異なるSupabaseクライアントを使用する必要があります。

通常の環境では以下のような設定になります。

環境 用途 クライアント種別
ブラウザ クライアントサイド Browser Client
サーバー サーバーサイド Server Client

Docker環境での課題

Docker環境では、この公式推奨設定に加えて、ネットワークアクセスの違いを考慮する必要があります。

環境 URL 用途 アクセス方法
ブラウザ http://127.0.0.1:54321 クライアントサイド ホストマシンのSupabaseに直接アクセス
Docker Container http://host.docker.internal:54321 サーバーサイド Dockerネットワーク経由でホストアクセス

Docker環境特有の制約

  1. Dockerネットワーク: コンテナ内から 127.0.0.1localhost はコンテナ自身を指すため、ホストのサービスにアクセスできません
  2. ブラウザの制約: ブラウザは host.docker.internal というホスト名を解決できません
  3. OAuth認証の複雑性: OAuth認証フローでは、この2つの異なるアクセス方法が混在するため問題が発生します

Docker環境でのOAuth認証の課題

Docker環境でOAuth認証を実装すると、以下のような問題が発生します。

問題のあるOAuth認証フロー

この問題を具体的に見てみましょう。適切な設定をせずにDocker環境でOAuth認証を実装すると、以下のような流れで問題が発生します。

問題の詳細

問題の流れを整理すると以下のようになります。

  1. ユーザーがログインボタンクリック → Server Action実行
  2. PKCE code verifier生成 → PKCEフロー用のcode verifierを生成・保存
  3. Docker内でSupabaseにOAuth要求signInWithOAuth()host.docker.internalでアクセス
  4. SupabaseがOAuth URL返却host.docker.internalを含むGoogle認証URL
  5. ❌ 問題発生 → ブラウザがアクセス不可能なURLでリダイレクト
  6. ❌ DNS解決失敗 → ブラウザはhost.docker.internalを解決できない
  7. ❌ 認証フロー停止 → Google OAuth画面に到達できず、認証が失敗

つまり、Docker環境でのネットワークアクセスの違いが根本的な原因です。公式の推奨設定(サーバー・クライアント分離)に加えて、Docker環境特有のネットワーク課題を解決する必要があります。

解決策の実装

ここからは、この問題をどう解決するかを見ていきましょう。

環境変数とSupabaseクライアントの設定

公式ドキュメントの推奨に従い、サーバーサイドとクライアントサイドで異なるSupabaseクライアントを作成します。

Docker環境では、これに加えて異なるURLを使用する必要があります。

  • NEXT_PUBLIC_SUPABASE_URL: ブラウザ用(http://127.0.0.1:54321
  • SUPABASE_INTERNAL_URL: サーバー用(http://host.docker.internal:54321

これらの環境変数を使って、適切なSupabaseクライアントを作成します。ここでもう一つ重要な点があり、それはCookieの名前を統一することです。デフォルトではSupabase APIのURLをもとに設定されるため、クライアントサイドで認証情報が取得できなくなってしまいます。そのため、どちらも同じ名前でアクセスできるように明示的に設定する必要があります。

クライアントサイドの実装例

src/lib/supabase/client.ts
export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!, // ブラウザ用URL
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookieOptions: { name: "auth-token" }, // 重要: サーバーと統一
    }
  );
}

サーバーサイドの実装例

src/lib/supabase/server.ts
export async function createClient() {
  return createServerClient(
    process.env.SUPABASE_INTERNAL_URL!, // Docker内用URL
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookieOptions: { name: "auth-token" }, // クライアントと統一
      // Cookie管理の実装...
    }
  );
}

OAuth URLの変換処理

Docker環境では、Supabaseから返されるOAuth URLの変換処理が必要になります。これがDocker環境特有の問題を解決する重要な実装の一つです。

src/app/actions/auth.ts
'use server'

import { createClient } from "@/lib/supabase/server";

export async function signInWithGoogle() {
  const supabase = await createClient();
  
  const { data } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${process.env.WEB_URL}/auth/callback`,
    },
  });

  if (data.url) {
    // 🚨 CRITICAL: Docker環境特有のURL変換ロジック
    const browserUrl = data.url.replace(
      process.env.SUPABASE_INTERNAL_URL!, // Docker内URL
      process.env.NEXT_PUBLIC_SUPABASE_URL! // ブラウザ用URL
    );
    redirect(browserUrl); // ブラウザがアクセス可能なURLにリダイレクト
  }
}

この変換処理がDocker環境で必要な理由は以下の通りです。

  1. サーバーアクション実行環境: Docker内で実行されるため、host.docker.internalでSupabaseにアクセス
  2. OAuth URL生成: Supabaseがサーバーサイドの設定(host.docker.internal)をもとにOAuth URLを生成
  3. ブラウザの制限: ブラウザはhost.docker.internalというホスト名を解決できない
  4. URL変換で解決: ブラウザでアクセス可能な127.0.0.1形式に変換してリダイレクト

公式のサーバー・クライアント分離設定は正しく動作しますが、Docker環境ではこの追加的な変換処理が必要になります。

Docker設定

Docker Composeでの重要な設定は以下の通りです。

services:
  web:
    extra_hosts:
      - host.docker.internal:host-gateway  # ホストマシンアクセス用

この設定により、Docker内からhost.docker.internalでホストマシンのSupabaseにアクセスできます。

解決後のOAuth認証フロー

上記の解決策を実装すると、Docker環境でも正常にOAuth認証が動作するようになります。

成功パターンでは以下のような流れになります。

  1. ユーザーがログインボタンクリック → Server Action実行
  2. PKCE code verifier生成 → PKCEフロー用のcode verifierを生成・保存
  3. Docker内でSupabaseにOAuth要求signInWithOAuth()host.docker.internalでアクセス
  4. SupabaseがOAuth URL返却host.docker.internalを含むGoogle認証URL
  5. ✅ URL変換処理実行 → ブラウザでアクセス可能な127.0.0.1に変換
  6. ✅ ブラウザリダイレクト → GoogleのOAuth画面へ正常にアクセス
  7. Google OAuth認証 → ユーザーがGoogle認証を実行
  8. 認証完了 → Google認証完了、authorization code付与(5分間有効)
  9. コールバック処理 → Next.jsの/auth/callbackエンドポイントに戻る
  10. ✅ セッション確立exchangeCodeForSession()でcode + verifierをセッションに交換、Cookieに保存
  11. ✅ アプリケーションに戻る → 認証済み状態でアプリケーション画面へ

重要な改善点

  • OAuth URL変換処理により、ブラウザがアクセス可能なURLでリダイレクト
  • Cookie名の統一により、クライアント・サーバー間でセッション情報が正しく共有
  • Docker設定により、コンテナ内からホストのSupabaseに正常アクセス
  • 公式推奨設定(サーバー・クライアント分離)とDocker環境対応の組み合わせ

PKCEフローでのauthorization codeとcode verifierの両方を使用したセッション確立が、同じブラウザ・デバイスで正常に実行されるようになります。

まとめ

Docker環境でSupabase + Next.jsを組み合わせる際の重要なポイントをまとめると

  1. 公式推奨設定の遵守: サーバー・クライアント分離設定は必須
  2. OAuth URL変換: 認証フローでブラウザがアクセス可能なURLへの変換が必要
  3. Cookie名の統一: クライアント・サーバー間でCookie名を一致させる
  4. Docker設定: host.docker.internal:host-gatewayでホストアクセスを有効化

これらの実装パターンを組み合わせることで、公式の推奨設定だけでは解決できないDocker環境特有の課題を解決できます。

Discussion