Docker環境でSupabase OAuth認証が失敗する問題と解決策【Next.js + Google OAuth】
はじめに
Docker環境でSupabaseのOAuth認証を導入する場合、公式のドキュメント通りに進めても様々な問題に遭遇します。
これは、Dockerコンテナ内とブラウザでSupabaseへのアクセス方法が異なるためです。
本記事では、このDocker環境特有の課題を解決する具体的な手順を、実装コードと共に詳しく解説します。同じ問題で困っている方の参考になれば幸いです。
事前準備
- Next.jsとSupabaseの基本的な設定知識(公式クイックスタート)
- Google OAuth設定の経験(公式ガイド)
完全なサンプルコード
この記事で解説するすべてのコードは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環境特有の制約
-
Dockerネットワーク: コンテナ内から
127.0.0.1やlocalhostはコンテナ自身を指すため、ホストのサービスにアクセスできません -
ブラウザの制約: ブラウザは
host.docker.internalというホスト名を解決できません - OAuth認証の複雑性: OAuth認証フローでは、この2つの異なるアクセス方法が混在するため問題が発生します
Docker環境でのOAuth認証の課題
Docker環境でOAuth認証を実装すると、以下のような問題が発生します。
問題のあるOAuth認証フロー
この問題を具体的に見てみましょう。適切な設定をせずにDocker環境でOAuth認証を実装すると、以下のような流れで問題が発生します。
問題の詳細
問題の流れを整理すると以下のようになります。
- ユーザーがログインボタンクリック → Server Action実行
- PKCE code verifier生成 → PKCEフロー用のcode verifierを生成・保存
-
Docker内でSupabaseにOAuth要求 →
signInWithOAuth()をhost.docker.internalでアクセス -
SupabaseがOAuth URL返却 →
host.docker.internalを含むGoogle認証URL - ❌ 問題発生 → ブラウザがアクセス不可能なURLでリダイレクト
-
❌ DNS解決失敗 → ブラウザは
host.docker.internalを解決できない - ❌ 認証フロー停止 → 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をもとに設定されるため、クライアントサイドで認証情報が取得できなくなってしまいます。そのため、どちらも同じ名前でアクセスできるように明示的に設定する必要があります。
クライアントサイドの実装例
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!, // ブラウザ用URL
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookieOptions: { name: "auth-token" }, // 重要: サーバーと統一
}
);
}
サーバーサイドの実装例
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環境特有の問題を解決する重要な実装の一つです。
'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環境で必要な理由は以下の通りです。
-
サーバーアクション実行環境: Docker内で実行されるため、
host.docker.internalでSupabaseにアクセス -
OAuth URL生成: Supabaseがサーバーサイドの設定(
host.docker.internal)をもとにOAuth URLを生成 -
ブラウザの制限: ブラウザは
host.docker.internalというホスト名を解決できない -
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認証が動作するようになります。
成功パターンでは以下のような流れになります。
- ユーザーがログインボタンクリック → Server Action実行
- PKCE code verifier生成 → PKCEフロー用のcode verifierを生成・保存
-
Docker内でSupabaseにOAuth要求 →
signInWithOAuth()をhost.docker.internalでアクセス -
SupabaseがOAuth URL返却 →
host.docker.internalを含むGoogle認証URL -
✅ URL変換処理実行 → ブラウザでアクセス可能な
127.0.0.1に変換 - ✅ ブラウザリダイレクト → GoogleのOAuth画面へ正常にアクセス
- Google OAuth認証 → ユーザーがGoogle認証を実行
- 認証完了 → Google認証完了、authorization code付与(5分間有効)
-
コールバック処理 → Next.jsの
/auth/callbackエンドポイントに戻る -
✅ セッション確立 →
exchangeCodeForSession()でcode + verifierをセッションに交換、Cookieに保存 - ✅ アプリケーションに戻る → 認証済み状態でアプリケーション画面へ
重要な改善点:
- OAuth URL変換処理により、ブラウザがアクセス可能なURLでリダイレクト
- Cookie名の統一により、クライアント・サーバー間でセッション情報が正しく共有
- Docker設定により、コンテナ内からホストのSupabaseに正常アクセス
- 公式推奨設定(サーバー・クライアント分離)とDocker環境対応の組み合わせ
PKCEフローでのauthorization codeとcode verifierの両方を使用したセッション確立が、同じブラウザ・デバイスで正常に実行されるようになります。
まとめ
Docker環境でSupabase + Next.jsを組み合わせる際の重要なポイントをまとめると
- 公式推奨設定の遵守: サーバー・クライアント分離設定は必須
- OAuth URL変換: 認証フローでブラウザがアクセス可能なURLへの変換が必要
- Cookie名の統一: クライアント・サーバー間でCookie名を一致させる
-
Docker設定:
host.docker.internal:host-gatewayでホストアクセスを有効化
これらの実装パターンを組み合わせることで、公式の推奨設定だけでは解決できないDocker環境特有の課題を解決できます。
Discussion