Next.jsとSupabaseでGoogle認証【@supabase/ssr】

※当サイトは、アフィリエイト広告を利用しています
JavaScript/TypeScript
スポンサーリンク

この記事では、Next.jsのApp RouterとSupabaseでGoogleログインを実装する方法を紹介します。「Auth Helpers for Next.js」が以前まで使われていましたが、現在は@supabase/ssrの使用が推奨されています。

この記事では、公式ドキュメントに沿いながら、フロント/サーバーそれぞれでSupabaseクライアントを使い分ける構成を紹介します。

スポンサーリンク

Next.jsとSupabaseでのGoogle認証ログイン実装の流れ

流れは以下のようになっています。

  1. Google Cloud PlatformとSupabaseで事前準備
  2. 必要パッケージのインストール
  3. Supabaseクライアントの作成
  4. ログインボタンの実装
  5. 認証後のコールバック処理(cookie保存)

認証の流れをメインにしたいのでスタイルやページなどは最低限のもので進めます。

スポンサーリンク

Google Cloud PlatformとSupabaseで事前準備

まずはGoogle CloudとSupabaseで事前準備をします。

設定が必要な項目は以下になります。

  • Google Cloud Platform
    • プロジェクト作成
    • APIとサービスからWebアプリケーションの認証情報を作成する
    • クライアントIDとクライアントシークレットをSupabase登録用にコピーする
    • 承認済みのリダイレクト URIにSupabaseのCallback URLを貼り付ける
  • Supabase
    • AuthenticationのAuth ProviderからGoogleをEnableに変更する
    • クライアントIDとクライアントシークレットをGoogle Cloud Platformでコピーしたものを貼り付ける
    • Callback URL (for OAuth)をコピーする

これが設定できれば事前準備は完了です。

必要パッケージのログイン

続いてNext.jsで必要なパッケージをインストールします。

npm install @supabase/supabase-js @supabase/ssr

「@supabase/supabase-js」と「@supaba/ssr」が必要なのでこの2つをインストールします。

続いて、.env.localファイルに以下2つの定数を定義します。

NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key

NEXT_PUBLIC_SUPABASE_URL」にはプロジェクトのURLを、「NEXT_PUBLIC_SUPABASE_ANON_KEY」にはanon_keyを入れます。

anon_keyはAPI Keysページにあります。

Supabaseのanon_key取得

Supabaseクライアントの準備

続いては、Supabaseクライアントの準備をします。

Supabaseクライアントは、クライアント側とサーバー側用に分けて管理するのがApp Router構成では基本となっています。

Next.js(特にApp Router構成)では、クライアントとサーバーの境界が明確に分かれているため、Supabaseの認証やデータ取得も、それぞれの適した環境で処理する必要があります。

クライアント側のSupabaseクライアント作成

まずはクライアント側のsupabaseクライアントを作成します。

/lib/supabase/client.tsファイルを準備します。

'use client'

import { createBrowserClient } from '@supabase/ssr'

export const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

ログインやログアウトなど、ユーザーの操作を扱うClient Componentで使用します。

サーバー側のSupabaseクライアント作成

続いてはサーバー側になります。

/lib/supabase/server.tsファイルを準備します。

import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = 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 {
            
          }
        },
      },
    }
  )
}

サーバー上でセッションやユーザー状態を扱うためのクライアントになります。

App Routerの Server Component や Server Action で利用します。

2つのSupabaseクライアントの使い分け

ざっくり2つの使い分けは以下のようになります。

処理Supabaseクライアント主な利用場所
Googleログイン/OAuth認証開始createBrowserClient(クライアント用)Client Component(ブラウザ操作)
セッション保存(Cookieに記録)createServerClient(サーバー用)認証コールバック後のServer Component
ログインユーザー取得createServerClientHeaderやDashboardなどのServer Component
ログアウト処理createServerClientServer ActionやAPI Route
Supabaseからのデータフェッチ(認証付き)createServerClientServer Component(セッションに基づく制限が必要なとき)
ローカルに保存されたセッションで操作する(例:プロフィール変更)createBrowserClientClient Component(ユーザー操作ありのとき)

Googleログイン処理

続いては先ほど作成したSupabaseクライアントを使って実際にGoogleログイン処理を実装していきます。

ログインボタン

まずはログインボタンを作成します。

'use client'

import { supabase } from '@/lib/supabase/client'

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

  return <button onClick={handleLogin}>Googleでログイン</button>
}

クライアント側のSupabaseクライアントを使ってOAuth認証を開始します。

認証後は redirectTo に指定したURLに遷移され、Supabaseがアクセストークンを返してくれます。

今回は「${location.origin}/auth/callback」にアクセストークンを返します。

コールバック処理(セッション保存)

ここでサーバー側のSupabaseクライアントを使い、セッションを保存してリダイレクトする処理を書きます。

先ほどリダイレクトURLを「/auth/callback」としているので、「app/auth/callback/route.ts」ファイルを作成し、以下のように記述します。

import { NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'

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

  if (!next.startsWith('/')) {
    next = '/'
  }

  if (code) {
    const supabase = await createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)

    if (!error) {
      const forwardedHost = request.headers.get('x-forwarded-host')
      const isLocalEnv = process.env.NODE_ENV === 'development'
      const redirectUrl = isLocalEnv
        ? `${origin}${next}`
        : forwardedHost
        ? `https://${forwardedHost}${next}`
        : `${origin}${next}`

      return NextResponse.redirect(redirectUrl)
    }
  }

  return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}

今度はサーバー側のSupabaseクライアントを使って、セッション情報をcookieに保存しています。

「supabase.auth.exchangeCodeForSession(code)」の箇所が実際に保存している処理です。

そして次のページへリダイレクトさせています。

事前準備含め正常に設定できていれば、cookieが保存された状態でリダイレクト先のページに遷移されるはずです。

おわりに

App Router環境ではクライアントとサーバーでSupabaseクライアントを明確に分けて使う必要があります。@supabase/ssrを使えば、公式が推奨するセッション管理方式に従った安全な実装ができます。

公式ドキュメントも整備されているので、状況に応じてそちらも参照してください。

タイトルとURLをコピーしました