🐶

Supabase Authを使ったNext.jsアプリの認証と認可(1/2)

2022/03/02に公開

前回、Next.jsでSupabaseを使って認証し、データを更新するまで手順をまとめた記事を作成しました。

https://zenn.dev/hrtk/articles/3da84e46c97267

前の記事では実装の手順だけを示しただけでしたので、今回はマジック・リンクによる認証を実装しながら、処理内容をもう少し詳しくみていきます。

認証と認可

Supabaseでは、Postgresを使って、Authの仕組みを提供しています。

Authの仕組みとして、大きく2つの要素があります。

  • 認証:ユーザーを入れていいのか。入れた場合、そのユーザーは何者なのか。
  • 認可:入ったユーザーは、何をできるのか。どのデータにアクセスや操作できるか。

最初は認証に関して、それから認可について、詳しく順にみていきます。

Supabaseプロジェクトを作成

まず、新しくSupabaseでプロジェクトを作成します。Supabaseのサインアップがまだの場合は、こちらより登録してください。

  • Organization: プロジェクトが所属する組織を選択します。
  • Name: プロジェクトの名前を入力します。
  • Database Password: データベースに使用するパスワードを入力します。
  • Region: 想定されるアプリを使用するユーザーにネットワーク的に近い地域を選択します。日本の場合は、「Northeast Asia (Tokyo)」(東アジア - 東京)を選択します。
  • Pricing Plan: 1組織あたり2つまでは無料で作成できます。枠が残っている場合は、「Free tier」(無料枠)を選択できます。

「Create new project」ボタンをクリックして、新しくプロジェクトを作成します。

作成できるまでしばらく待ちます。

プロジェクトが作成できたら、後ほどアプリで必要になるキーとURLを保持します。「Settings」の「API」から次の箇所より取得できます。

  • 「Project API keys」欄の「anon」キー
  • 「Config」欄の「URL」

Next.jsアプリをセットアップ

Next.jsとSupbaseクライアントの初期設定を済ませたリポジトリーを用意したので、そちらをベースに手順を解説します。Tailwindcssの初期設定も含んでいるので、ボタンの体裁などのスタイルはそちらを利用します。

https://github.com/hirotaka/examples/tree/atlanta-0.0.1/supabase-nextjs-database

degit経由で取得して、必要なパッケージをインストールします。

npx degit hirotaka/examples/supabase-nextjs-database#atlanta-0.0.1 supabase-nextjs-database
cd supabase-nextjs-database
npm install

環境変数をセットするために.env.localファイルを作成します。

cp .env .env.local

先ほどSupbaseのダッシュボードで控えた、anonキーをNEXT_PUBLIC_SUPABAE_ANON_KEYに、URLをNEXT_PUBLIC_SUPABASE_URLにセットします。

次のような形式の記述になります。

NEXT_PUBLIC_SUPABASE_ANON_KEY=xxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co

開発サーバーを起動します。

npm run dev

ブラウザでローカルのアドレスに接続します。

http://localhost:3000


「Hello, world!」というメッセージが表示されます。

マジック・リンクで認証

それでは、準備ができたので、マジック・リンクによる認証を実装していきます。

コンテキストを作成

ユーザーのログインやログアウト処理やユーザーに関するセッションなどの情報をまとめてコンテキストに切り出しておきます。

contexts/user.tsxという名前でファイルを作成します。

contexts/user.tsx
import { createContext, useState, useContext } from 'react'
import { supabase } from '@/lib/supabase-client'

const Context = createContext()

const Provider = ({ children }) => {
  const [user, setUser] = useState()
  const [loading, setLoading] = useState(false)

  const login = async (email) => {
    console.log('ログイン!')
    setUser({ email })
  }

  const logout = async () => {
    console.log('ログアウト!')
    setUser(null)
  }

  const exposed = {
    login,
    logout,
    user,
    setUser,
    loading,
  }

  return <Context.Provider value={exposed}>{children}</Context.Provider>
}

export const useUser = () => useContext(Context)

export default Provider

いったん、ログインとログアウトには仮の処理をいれておきます。

アプリ内のどのコンポーネントからでも呼び出せるように、pages/_app.tsxにコンテキストのプロバイダをセットします。

pages/_app.tsx
import type { AppProps } from 'next/app'
import '@/styles/globals.css'
import UserProvider from '@/contexts/user'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <UserProvider>
      <Component {...pageProps} />
    </UserProvider>
  )
}

export default MyApp

ログイン・コンポーネントを作成

ログイン・フォームを表示するコンポーネントを下記のようにします。

components/auth.tsx
import { useState } from 'react'
import { useUser } from '@/contexts/user'

export default function Auth() {
  const { loading, login } = useUser()
  const [email, setEmail] = useState('')

  return (
    <div className="max-w-md mx-auto">
      <h1 className="text-2xl">ログイン</h1>
      <p className="mt-4">
        Eメールを入力してマジック・リンクでログインします。
      </p>
      <div className="mt-6">
        <label className="block">Eメール</label>
        <input
          className="mt-2 w-full"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <button
        className="primary mt-4"
        onClick={() => login(email)}
        disabled={loading}
      >
        {loading ? '読み込み中...' : 'マジックリンクを送信'}
      </button>
    </div>
  )
}

ダッシュボード・コンポーネントを作成

ログインした後に、表示するダッシュボードのコンポーネントを次のようにします。

components/dashboard.tsx
import { useUser } from '@/contexts/user'

export default function Dashboard() {
  const { logout, user } = useUser()

  return (
    <div className="max-w-md mx-auto">
      <h1 className="text-2xl">ダッシュボード</h1>
      <div className="mt-6">
        <p>Hello, {user.email}!</p>
      </div>
      <div className="mt-6">
        <button className="secondary" onClick={() => logout()}>
          ログアウト
        </button>
      </div>
    </div>
  )
}

トップ画面を作成

トップ画面をユーザーの状態でコンポーネントを切り替えるようにします。

pages/index.tsx
import Auth from '@/components/auth'
import Dashboard from '@/components/dashboard'
import { useUser } from '@/contexts/user'

export default function Home() {
  const { user } = useUser()

  return (
    <div className="max-w-7xl p-10 m-auto">
      {!user ? <Auth /> : <Dashboard />}
    </div>
  )
}

これで、Eメールを入力し、「マジック・リンクを送信」ボタンをクリックすると、ダッシュボードへ遷移する様になります。

認証処理を追加

続いて、実際にSupabaseでログインとログアウトができるように処理を実装します。

ログインのメソッドは次のようになります。

contexts/user.tsx
const login = async (email) => {
  try {
    setLoading(true)
    const { error } = await supabase.auth.signIn({ email })
    if (error) throw error
    alert('ログインのリンクをEメールで確認してください!')
  } catch (error) {
    alert(error.error_description || error.message)
  } finally {
    setLoading(false)
  }
}

auth.singIn()メソッドにemailを渡して呼びだすことで、マジック・リンクで認証します。

これで、ログイン処理が実行されてメールが送信されます。

Eメールのリンクをクリックして、遷移してきた際にuseEffectで認証状態の変化を検知して、認証済みのユーザーをセットします。

contexts/user.tsx
useEffect(() => {
  setUser(supabase.auth.user())
  supabase.auth.onAuthStateChange(() => {
    setUser(supabase.auth.user())
  })
}, [])

onAuthStateChange()で認証情報の変更を検知して、ユーザーをセットします。ユーザーがセットされると、pages/index.tsxでコンポーネントをダッシュボードに切り替えます。

ブラウザーの再読み込みした際、auth.onAuthStateChange()メソッドの中が実行されません。そのため、メッソドの外にも認証情報を取得してsetUser()することでログイン状態を保つことができます。

ログアウト処理は、auth.signOut()メソッドを呼びだして、ユーザーをクリアします。

contexts/user.tsx
const logout = async () => {
  await supabase.auth.signOut()
  setUser(null)
}

最終的に、contexts/user.tsxファイルは次のようになります。

https://github.com/hirotaka/examples/blob/atlanta-0.0.2/supabase-nextjs-database/contexts/user.tsx

ここまでのコード全体はこちらで確認できます。

https://github.com/hirotaka/examples/tree/atlanta-0.0.2/supabase-nextjs-database

動作を確認

はじめて、入力するEメールの場合は、登録の確認メールが送信されます。

Eメールのリンクをクリックするとユーザーとしてログインします。

ログアウトをして、もう一度、マジック・リンクを送信すると、マジック・リンクのEメールが送信されることを確認できます。

また、Eメールのリンクをクリックすることでログインできます。

これらのEメール本文を変更したい場合は、「Authentication」の「Template」で変更できます。

ユーザーの認証データについて

Supbaseのダッシュボードで、「Authentication」の「Users」で新しくユーザーが作成されているのを確認できます。

こここでは、よく使用される参照されるデータだけ表示されています。内部的にはいくつかのテーブルが存在し、authスキーマ内に作成されます。

「Table editor」のスキーマ選択で「auth」を選択すると、テーブルの中身を参照できます。

これらのテーブルを直接操作することはあまりないですが、どのようなテーブルやカラムが在るかを確認できます。

おわりに

マジック・リンクによる認証を実装しながら、Supbase Authの認証部分を詳細にみてきました。
次回は、ログインしたユーザーがデータベースのデータを操作するための認可部分の処理を紹介します。

https://zenn.dev/hrtk/articles/supabase-nextjs-database-authorization

参考

Supbaseの知識を深めるために、ドキュメントの翻訳に取り組んでいます。

Supabase Authに関しては、こちらも参考にしくてださい。

https://www.supabase.jp/docs/guides/auth

GitHubで編集を提案

Discussion