🔐

Next14でSupabase Login

2024/10/20に公開

簡単だけど難しい

Next14とSupabaseを使ったことがある方を対象としております。

公式ドキュメントを読んで機能を実装しようとしたが、TypeScriptだとデータ型の定義が必要だったり、AppRouterだと新しくて情報があまりなくてどれがいいのかと悩みました💦

最近増えてきた気もしますが、Auth.jsなるものを使ったり他のパッケージでログインするものが多かったりする。あまりパッケージに頼りたくない💦

今回も使ってはいるが...

対象者

  • Supabaseで認証機能を使ってみたい
  • 少ないコードで仕組みを理解したい
  • Next14を学習してる人

AIに頼って機能実装しようとするものの中々うまくいかない。使わない方がいいのか...

メモ書き程度なので参考程度にみてください🙏

やること

  1. プロジェクトを作成したら必要なパッケージを追加する
  2. Supabaseのメールアドレス&パスワードログインの設定をしておく

.env.localにURL, anon keyの設定をする。

NEXT_PUBLIC_SUPABASE_URL=https://*************.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6*****************

パッケージを追加する。

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

でもこっちが必要みたいだ

npm i @supabase/ssr

認証の設定

設定ファイルや必要なコードは全てapp/配下に作成します。
app/lib/supabase-client.tsを作成。

import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export const createClient = () => createClientComponentClient()

ログイン後のページ

app/dashboard/page.tsxを作成する。User型のデータがないとエラーが出たので追加した。

'use client'

import { useEffect, useState } from 'react'
import { createClient } from '../lib/supabase-client'
import { useRouter } from 'next/navigation'
import { User } from '@supabase/supabase-js'

export default function Dashboard() {
  const [user, setUser] = useState<User | null>(null)
  const router = useRouter()
  const supabase = createClient()

  useEffect(() => {
    const getUser = async () => {
      const { data: { user } } = await supabase.auth.getUser()
      if (user) {
        setUser(user)
      } else {
        router.push('/login')
      }
    }

    getUser()
  }, [supabase, router])

  const handleLogout = async () => {
    await supabase.auth.signOut()
    router.push('/login')
  }

  if (!user) return <div>Loading...</div>

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {user.email}!</p>
      <button onClick={handleLogout}>ログアウトする</button>
    </div>
  )
}

新規作成・ログインのページを作成

こちらはlogin/のページとして作成していましたが、コンポーネントとして使って最初のページに表示してます。説明のページを見た後にログインページに来るのが望ましいかも。

今回は練習ということでハードコーディングになってしまいましたが、ユーザーの新規作成・ログインを体験できると思います。

'use client'

import { useState } from 'react'
import { createClient } from '../lib/supabase-client'
import { useRouter } from 'next/navigation'

export default function Login() {
  const [loading, setLoading] = useState(false)
  const router = useRouter()
  const supabase = createClient()

  const handleLogin = async () => {
    try {
      setLoading(true)
      const { error } = await supabase.auth.signInWithPassword({
        email: 'hoge@co.jp',
        password: '1234qq',
      })

      if (error) throw error
      router.push('/dashboard')
    } catch (error) {
      console.error('Error logging in:', error)
      alert('Failed to log in')
    } finally {
      setLoading(false)
    }
  }

  const handleSignUp = async () => {
    try {
      setLoading(true)
      const { error } = await supabase.auth.signUp({
        email: 'hoge@co.jp',
        password: '1234qq',
      })

      if (error) throw error
      router.push('/dashboard')
    } catch (error) {
      console.error('Error logging in:', error)
      alert('Failed to log in')
    } finally {
      setLoading(false)
    }
  }

  return (
    <div>
      <h1>Login</h1>
      <button onClick={handleSignUp} disabled={loading}>
        {loading ? 'Loading...' : '新規登録する'}
      </button><br/>
      <button onClick={handleLogin} disabled={loading}>
        {loading ? 'Loading...' : 'ログインする'}
      </button>
    </div>
  )
}

こちらが最初に表示されるpage.tsxです。こちらでLoginコンポーネントを読み込みましょう。

import Login from "./login/page";

export default function Home() {
  return (
   <div>
     <Login />
   </div>
  );
}

動作確認してみましょう。

http://localhost:3000/

新規作成、ログイン前。

ログインしたページ

Supabaseのコンソールを確認するとユーザーのアカウントが登録されておりました。メソッドは実行できている。

まとめ

Auth.jsなるものを使わずに認証機能を実装してみました。この変は難しいですね。簡単には作れないですね💦
Prismaを使ったデータベースとのやり取りはなんとなくわかってきたが、認証はまだ理解できていない部分が多いですね。
色々なパターンがあるからどれが最適なのか...

先ほど追加したパッケージだともう非推奨みたい💦
公式によると...

https://supabase.com/docs/guides/auth/auth-helpers/nextjs

Supabase Auth with the Next.js App Router

The auth-helpers package has been replaced with the @supabase/ssr package. We recommend setting up Auth for your Next.js app with @supabase/ssr instead. See the Next.js Server-Side Auth guide to learn how.

https://www.npmjs.com/package/@supabase/auth-helpers-nextjs

Next.jsアプリルータを使ったSupabase Auth

auth-helpersパッケージは@supabase/ssrパッケージに置き換えられました。Next.jsアプリのAuthは、@supabase/ssrで設定することをおすすめします。その方法については、Next.js Server-Side Auth ガイドをご覧ください。

これか???
https://supabase.com/docs/guides/auth/server-side

https://www.npmjs.com/package/@supabase/ssr

package.jsonの内容は今はこんな感じになっております。

{
  "name": "post-writer-webapp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@prisma/client": "^5.21.1",
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/ssr": "^0.5.1",
    "@supabase/supabase-js": "^2.45.6",
    "next": "14.2.15",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20.16.13",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "prisma": "^5.21.1",
    "tailwindcss": "^3.4.1",
    "ts-node": "^10.9.2",
    "typescript": "^5"
  },
  "prisma": {
    "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
  }
}

app/の外に配置したこのコードを使わなくても動くようだ...

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

export 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(e) {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
            console.error(`supabase connet error: ${e}`)
          }
        },
      },
    }
  )
}

Discussion