Next.js + NextAuthでメールアドレス/パスワード認証をする

4 min read読了の目安(約4000字

NextAuth は Next.js に簡単に認証機能を追加できるライブラリです。TwitterやGoogleといった有名サービスのOAuth認証や、パスワードが不要なメールリンク認証などを簡単に組み込むことができます。

NextAuth はパスワードレス(パスワードなし)の認証を推進しているため、従来のようなメールアドレスとパスワードを用いた認証機能がデフォルトで用意されていません。今回はメールアドレスとパスワードでの簡易的な認証機能を作成する方法を紹介します。

注意
今回の方法で認証する場合、セッション管理はデータベースではなく JSON Web トークン(JWT)を使用して行われます。データベースでセッション管理を行いたい場合は、今回の方法は使えないのでご注意ください。

準備

Next.js アプリを作成

npx create-next-app password-auth-example
cd password-auth-example

必要なパッケージをインストール

npm i next-auth

[...nextauth].js ファイルを pages/api/auth に追加

mkdir pages/api/auth
touch "pages/api/auth/[...nextauth].js"

APIの設定

pages/api/auth/[...nextauth].js

pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

// credentials の情報から、ログイン可能か判定してユーザー情報を返す関数
const findUserByCredentials = credentials => {
  // 今回は簡易的な例なのでメールアドレスとパスワードが一致する場合にユーザー情報を返却する。
  // データベースでユーザーを管理している場合は、データベースからユーザーを取得して、パスワードハッシュを比較して判定するのがよいかと。
  if (
    credentials.email === process.env.USER_EMAIL &&
    credentials.password === process.env.USER_PASSWORD
  ) {
    // ログイン可ならユーザー情報を返却
    return { id: 1, name: "Taro" }
  } else {
    // ログイン不可の場合は null を返却
    return null
  }
}

// NextAuth に渡すオプション
const options = {
  // 認証プロバイダー
  providers: [
    Providers.Credentials({
      // 表示名 ('Sign in with ...' に表示される)
      name: "Email",
      // credentials は、ログインページで適切なフォームを生成するために使用されます。
      // 送信するフィールドを指定できます。(今回は メールアドレス と パスワード)
      credentials: {
        email: { label: "Email", type: "email", placeholder: "email@example.com" },
        password: { label: "Password", type: "password" },
      },
      // 認証の関数
      authorize: async credentials => {
        const user = findUserByCredentials(credentials)
        if (user) {
	  // 返されたオブジェクトはすべてJWTの`user`プロパティに保存される
          return Promise.resolve(user)
        } else {
	  // nullまたはfalseを返すと、認証を拒否する
          return Promise.resolve(null)
	  
	  // ErrorオブジェクトやリダイレクトURLを指定してコールバックをリジェクトすることもできます。
          // return Promise.reject(new Error('error message')) // エラーページにリダイレクト
          // return Promise.reject('/path/to/redirect')        // URL にリダイレクト
        }
      },
    }),
  ],
}

export default (req, res) => NextAuth(req, res, options)

ページの設定

pages/index.js

ログイン状態の場合は、「ユーザー名」と「Sign out ボタン」を表示し、
ログアウト状態の場合は、「Sign in ボタン」を表示します。

pages/index.js
import { signIn, signOut, useSession } from 'next-auth/client'

export default function Home() {
  const [ session, loading ] = useSession()

  if (loading) {
    return <div>Loading...</div>
  }
  
  return (
    <div>
      {session && (
        <>
          Signed in as {session.user.name} <br/>
          <button onClick={signOut}>Sign out</button>
        </>
      )}
      {!session && (
        <>
          Not signed in <br/>
          <button onClick={signIn}>Sign in</button>
        </>
      )}
     </div>
  )
}

pages/_app.js

セッション状態をページ間で共有できるようにするために、NextAuth.js の Provider を使用します。

pages/_app.js
import { Provider } from 'next-auth/client'

export default function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}

環境変数の設定

ローカル開発の場合、 .env.local ファイルにenv変数を追加します。
アプリのURLと、認証するユーザーのメールアドレスとパスワードを設定します。

.env.local
NEXTAUTH_URL=http://localhost:3000

USER_EMAIL=email@example.com
USER_PASSWORD=password123

サイトを確認

npm run dev を実行し、 localhost:3000 にアクセスするとWebサイトを確認できます。

ログインしていない状態なのがわかります。
Not signed in

Sign in ボタンを押すとログイン画面に遷移します。
Auth

環境変数に設定したメールアドレスとパスワードでログインするとホームに戻ります。
ログイン中なのでユーザー名が表示されます。
Signed in

Sign out ボタンを押すとログアウトします。

以上です。NextAuth でメールアドレス/パスワードの簡易的な認証機能を追加することができました!