Closed10

Supabaseのサーバーサイド認証

KeiKei

Next.js Auth Helpersとは

  • SessionをlocalStorageではなくCookieに保存するようにSupabase Authを設定する。
    → App Router全体(クライアント・サーバー)で認証機能を使用できるようになる。
  • セッションはSupabaseへのリクエストと一緒に自動的に送信される

※上記はあくまでApp Routerで使える仕組み

Pages RouterでAuth Helpersを使いたいときはここから

KeiKei

プロジェクト作成(自動設定)

下記コマンドを使えば、cookieベースの認証が備わったNext.jsプロジェクトを作成できる。

Terminal
npx create-next-app -e with-supabase

設定項目

  • Cookie-based Auth
  • Middleware to refresh user's session
  • Code Exchange Route
  • TypeScript
  • Tailwind CSS

説明は下記に記載
https://github.com/vercel/next.js/tree/canary/examples/with-supabase

Hidden comment
KeiKei

プロジェクト作成(マニュアル)

以下は自身でセットアップする手順
すでにNext.jsのプロジェクトが出来上がっている前提

Next.js Auth Helpers のインストール

npm install @supabase/auth-helpers-nextjs @supabase/supabase-js

環境変数の設定

.env.local
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

ミドルウェア上でセッション管理

プロジェクト直下にmiddleware.jsを作成
Supabaseクライアントをサーバー上で使うには、ユーザーの認証セッションがアクティブな状態を保証する必要がある。セッションはCookieで追跡されるため、これを読み取り必要に応じて更新する。

middleware.js
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

export async function middleware(req) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  // サーバーコンポーネントルートでは、必ずgetSessionを呼ぶ必要あり
  await supabase.auth.getSession()
  return res
}

https://nextjs.org/docs/app/building-your-application/routing/middleware

Code Exchangeによるサインイン管理

app/auth/callback/route.jsを作成し、ルートハンドラを実装する

app/auth/callback/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET(request) {
  const requestUrl = new URL(request.url)
  const code = requestUrl.searchParams.get('code')

  if (code) {
    const supabase = createRouteHandlerClient({ cookies })
    await supabase.auth.exchangeCodeForSession(code)
  }

  // URL to redirect to after sign in process completes
  return NextResponse.redirect(requestUrl.origin)
}
KeiKei

気になっていること

  • そもそも認証プロセスの流れがよくわかっていない。クライアント側とサーバー側の両方を使用しているのか?それともサーバー側のみで完結しているか?
  • SupabaseのAuth Helpersはサーバー上で認証を行っているという認識でよいのか?
  • Route Handlersについて知識があいまい

レンダリング

https://nextjs.org/docs/app/building-your-application/rendering#rendering-environments

ルートハンドラ

https://nextjs.org/docs/app/building-your-application/routing/route-handlers

KeiKei

Next.jsのルートハンドラに寄り道します!

Supabaseの認証を知るためにも、Route Handlersを知る必要がありそう。。。
https://nextjs.org/docs/app/building-your-application/routing/route-handlers

使い方

appディレクトリのroute.js|tsにルートハンドラを定義する。
GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONSの HTTPメソッドに対応

app/api/route.ts
export async function GET(request: Request) {}

キャッシュについて

  • Route Handlers は、ResponseオブジェクトでGETメソッドを使用するときデフォルトでキャッシュされる
app/items/route.ts
import { NextResponse } from 'next/server'
 
export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY,
    },
  })
  const data = await res.json()
 
  return NextResponse.json({ data })
}

Dynamic Functions

ルートハンドラでは、cookies , headers といったNext.jsが提供する動的関数を使用できる。
おそらくSupabaseの認証機能についてもここが関係している気がする。。。🤔

長くなってきたので、次のスクラップにかく。

KeiKei

Dynamic Funcionsについて見ていく

Next.jsが提供する、動的関数を見ていく。
いくつかあるがCookiesとHeadesを見る

Cookies

  • next/headersよりサーバー関数としてインポートする
  • cookiesを読み取ることができるようになる
  • Route Handler 内で直接呼び出し、または他の関数の中にネストして呼び出しの可能
  • インスタンスに新しくCookieを設定する際にはSet-Cookieヘッダを使用して、新しいResponseを返す必要あり。
app/api/route.ts
import { cookies } from 'next/headers'
 
export async function GET(request: Request) {
  // 読み取り専用のcookieインスタンスを生成する
  const cookieStore = cookies() 
  const token = cookieStore.get('token') 
 
  return new Response('Hello, Next.js!', {
    status: 200,
    headers: { 'Set-Cookie': `token=${token.value}` },
  })
}

また、Web APIs 上で抽象化を使用して、Cookieを読み取ることも可能

app/api/route.ts
import { type NextRequest } from 'next/server'
 
export async function GET(request: NextRequest) {
  const token = request.cookies.get('token')
}

Headers

  • next/headersよりサーバー関数としてインポートする
  • Route Handler 内で直接呼び出し、または他の関数の中にネストして呼び出し可能
  • Header インスタンスは読み取り専用のため、headersをセットするには新しいResponseを返す必要あり。
app/api/route.ts
import { headers } from 'next/headers'
 
export async function GET(request: Request) {
  const headersList = headers()
  const referer = headersList.get('referer')
 
  return new Response('Hello, Next.js!', {
    status: 200,
    headers: { referer: referer },
  })
}

こちらもcookesと同様に、Web APIs上で抽象化を行うことで、Headersを読み取ることが可能。

app/api/route.ts
import { type NextRequest } from 'next/server'
 
export async function GET(request: NextRequest) {
  const requestHeaders = new Headers(request.headers)
}
KeiKei

Middlewareについて

Cookiesを使う

  • リクエストでは、CookieヘッダーにCookiesが格納される
  • レスポンスでは、Set-CookieヘッダーにCookiesが格納される
  • Next.jsではNextRequestNextResponse上にある、cookies拡張を通してCookiesにアクセスしたり、操作したりできる。
    • Incoming Requestに対して、cookiesはget, getAll, set, delete メソッドを持っている。また、Cookieの存在を確認するhasメソッド、すべてのCookieを削除するclearメソッドがある
    • outgoing responseに対して、cookiesはget, getAll, set, delete メソッドを持っている。
middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // "Cookie:nextjs=fast" header がリクエストに存在することを想定する
  // `RequestCookies`APIを使ってリクエストからCookieを取得する
  let cookie = request.cookies.get('nextjs')
  console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
  const allCookies = request.cookies.getAll()
  console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
 
  request.cookies.has('nextjs') // => true
  request.cookies.delete('nextjs')
  request.cookies.has('nextjs') // => false
 
  // `ResponseCookies` APIを使ってcookiesを設定する
  const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')
  response.cookies.set({
    name: 'vercel',
    value: 'fast',
    path: '/',
  })
  cookie = response.cookies.get('vercel')
  console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
  // レスポンスは`Set-Cookie:vercel=fast;path=/test` headerを持っている
  return response
}

Headersを設定する

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // Clone the request headers and set a new header `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')
 
  // You can also set request headers in NextResponse.rewrite
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders,
    },
  })
 
  // Set a new response header `x-hello-from-middleware2`
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}
このスクラップは2023/12/13にクローズされました