コンテンツへスキップ

【Next.js】NextAuthを使ってメールアドレスとパスワードでログインできる認証機能を実装する

  • 未分類

NextAuthはNext.jsで人気の認証ライブラリで、非常に幅広い認証機能を提供してます。
GoogleやGithub、AppleやLINEなど様々なサービスと連携したログインが実装できるほか、メールアドレスやパスワードを利用した独自の認証方式を実装することもできます。

今回は、一般的なログイン機能でよくある「メールアドレス」と「パスワード」を使った認証機能の実装方法をまとめていきたいと思います。

各種バージョン情報

今回説明する方法は、それぞれ下記のバージョンでの実装となります。

  • ・Next.js ver.14
  • ・NextAuth ver.4

多くの情報はNext.js 13以前のものが多いため、Pages Routerでの実装方法ばかりですが、この記事中ではApp Routerでの実装方法で説明を進めて行きます。

NextAuthのインストール

// yarnの場合
yarn add next-auth

// npmの場合
npm install next-auth

最初に既に用意されたNext.jsのプロジェクト環境内で、NextAuthをインストールします。

NextAuth関連ファイルの作成

NextAuthを導入するにあたり、①「app/api/auth/[…nextauth]/route.js」②「lib/next-auth/authOptions.js」の2ファイルを作成します。

/root
 └ src
   ├ app
   │  ∟ api
   │     ∟ auth
   │        ∟ [...nextauth]
   │       ∟ route.js ①
   │
   ├ lib
      ∟ next-auth
          ∟ authOptions.js ②

①のファイルはNextAuth公式の指定パスとなっていますので、この通りに配置します。

②のファイルは①のファイルに利用する設定部分を切り離したファイルになります。
単純に利用する分で言えば①のファイルに直接記述してしまっても問題ないのですが、後程認証情報を取得する際に設定部分だけ分離しておいた方が便利なため上記のように切り離しておきます。
(ファイルパスは他ファイルから読み込むことができれば問題ないので、各自の整理方針に従って配置してもらって問題ないです。)

「app/api/auth/[…nextauth]/route.js」ファイルの記述内容

import NextAuth from 'next-auth/next'
import { authOptions } from '@/libs/next-auth/authOptions'

const handler = NextAuth(authOptions)

export {
  handler as GET,
  handler as POST
}

①のファイルは上記のように記述しておきます。
細かな設定はauthOptions.jsに任せてしまうので、本ファイルでは設定をインポートし、GETとPOSTでリクエストを受けられるようにしておきます。

「lib/next-auth/authOptions.js」の記述内容

import CredentialsProvider from 'next-auth/providers/credentials'
import { randomUUID, randomBytes } from 'crypto'

export const authOptions = {
  /* providers */
  providers: [
    // ユーザ用認証
    CredentialsProvider({
      id: 'user',
      name: 'User',
      credentials: {
        email: { label: 'メールアドレス', type: 'email', placeholder: 'メールアドレス' },
        password: { label: 'パスワード', type: 'password' }
      },
      async authorize(credentials) {
        const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/user`, {
          method: 'POST',
          body: JSON.stringify(credentials),
          headers: {
            'Content-Type': 'application/json'
          }
        })

        const user = res.json()

        if (res.ok && user) {
          return user
        }
        
        return null
      },
    }),
  ],

  /* callbacks */
  callbacks: {
  },

  /* secret */
  secret: process.env.NEXTAUTH_SECRET,

  /* jwt */
  jwt: {
    maxAge: 3 * 24 * 60 * 60,       // 3 days 
  },

  /* session */
  session: {
    maxAge: 30 * 24 * 60 * 60,      // 30 days
    updateAge: 24 * 60 * 60,        // 24 hours
    generateSessionToken: () => {
      return randomUUID?.() ?? randomBytes(32).toString("hex")
    }
  }
}

NextAuthは認証機能実装のため、様々なProviderを提供しています。
GoogleであればGoogleProvider、GithubであればGithubProviderなどなど…

自身のDBに格納されているメールアドレスとパスワードによる認証を実装する場合は、CredentialsProviderを利用します。

async authorize関数内にオリジナルの認証プログラムを実装します。
上記の例であれば、別に用意している認証用APIエンドポイントに対してcredentials(今回はemailとpassword)をPOSTリクエストで渡して認証を実行しています。

ログイン保持の仕組み

上記の設定でログインすると、認証成功した情報はJWT(JSON Web Token)としてクライアント側に保持され、このJWTが残っている間中は「ログインしているもの」として扱うことができます。

上記の場合は、JWTの有効期限を3日間にしていますので、JWT発行から3日後に再認証が必要になります。

対象のユーザがアクティブな状態でJWTの有効期限が切れた場合は、NextAuthの機能でセッション情報を取得した際に新しいJWTが発行されますので、サイト利用中に再度ログインさせられるということ避けられるようになっています。

ログイン機能の実装

さて次にアプリケーション側のログイン画面のコンポーネントを実装します。
NextAuthではビルトインのログイン画面を提供してくれるようなのですが、今回は自分のログイン画面を用意します。

'use client'
import { useForm } from 'react-hook-form'
import { signIn } from 'next-auth/react'

export default function FormWrapper() {
  // フォーム関連の変数定義
  const {
    control,
    handleSubmit
  } = useForm<InputProps>({
    defaultValues: {
      email: '',
      password: ''
    }
  })

  // フォーム送信処理
  const onSubmit = async (data) => {

    const result = await signIn('user', {
      redirect: false,
      email: data.email,
      password: data.password
    })

    if (result?.error) {
      // ログイン失敗時処理
    } else {
      // ログイン成功時トップページへリダイレクト
      location.href = '/'
    }
  }

  return (
    <>
      <form
        className="max-w-[450px] w-full mx-auto border rounded-xl p-4 shadow-md"
        onSubmit={handleSubmit(onSubmit)}
      >
        <div className="mb-5">
          <div className="font-bold mb-2">メールアドレス</div>
          <div className="flex items-start gap-8">
            <InputControl
              name="email"
              type="email"
              control={control}
            />
          </div>
        </div>

        <div className="mb-5">
          <div className="font-bold mb-2">パスワード</div>
          <div className="flex items-start gap-8">
            <InputControl
              name="password"
              type="password"
              control={control}
            />
          </div>
        </div>

        <div className="flex justify-center">
          <GreenButton
            element="button"
            className="max-w-[250px] py-3 text-sm"
            type="submit"
          >
            ログイン
          </GreenButton>
        </div>
      </form>
    </>
  )
}

認証に直接関係のない記述は極力省いているのですが、上記のようにログインフォームを実装できます。

NextAuthのログインを実行するためには、NextAuthのsignInメソッドを呼び出します。

このsignInメソッドにcredentialsとしてユーザが入力したemailとpasswordを渡してあげるだけで、先ほど用意したauthOptions.jsの設定で認証を実行してくれます。

また、一緒にredirect: falseを渡してあげることで認証成功後にNextAuthの認証後ページにリダイレクトされることなくログインのみを行ってくれます。

認証情報の取得

上記までの流れでログインが出来るようになりました。

今回のNextAuth設定でログインが完了したら、クライアント側に認証情報(ユーザ名やメールアドレスなど)がセッションに保持されます。

保持された認証情報は以下の方法で取得することができます。

getServerSession():サーバサイドコンポーネントで取得する場合

import { getServerSession } from 'next-auth'
import { authOptions } from '@/libs/next-auth/authOptions'

export default async function Page() {
  // 認証情報を取得
  const session = await getServerSession(authOptions)

  return (
    ...
  )
}

サーバサイドコンポーネントで認証情報を取得する場合は、NextAuthのgetServerSessionメソッドを利用します。

getServerSessionメソッドにNextAuthの設定を渡してやることで、セッションに格納されている認証情報を取得することができ、ログイン状態の判定を行うことができます。

SessionProvider / useSession():クライアントサイドコンポーネントで取得する場合

import { SessionProvider } from 'next-auth/react'

export default async function MyApp({ children }) {
  return (
    <html lang="en">
      <body>
        <SessionProvider>
          {children}
        </SessionProvider>
      </body>
    </html>
  )
}

クライアントサイドで認証情報を取得するため使用するuseSessionメソッドはSessionProviderの中でないと利用できません。

アプリケーションのルートのlayout.tsなど高階層のコンポーネントにSessionProviderを設置し、アプリケーション全体をSessionProviderで囲むといいでしょう。

'use client'
import { useSession } from "next-auth/react"

export default function Session() {
  const { data: session, status } = useSession()

  return (
    <>
    {
      (status === "authenticated") ?
        <div>ログインしてる</div>
      :
        <div>ログインしてない</div>
    }
    </>
  )
}

そして、子コンポーネントで上記のようにuseSessionメソッドを利用し、sessionとstatusを取得できるようになります。

上記はクライアントサイドコンポーネントのみでしか利用できないため、必ず’use client’を記述する必要があります。

ログアウトの方法

NextAuthはログアウトの方法も非常に簡単で以下のように用意されたメソッドを利用するだけです。

'use client'
import { signOut } from 'next-auth/react'

export default function Logout() {
  const handleLogout = () => {
    signOut()
  }

  return (
    <div>
      <button onClick={handleLogout}>ログアウト</button>
    </div>
  )
}

上記のようにNextAuthが用意してくれているsignOutメソッドを呼び出すだけで簡単にログアウトが行えます。

ただ、これはあくまでもクライアント側の認証情報を破棄するだけになりますので、データベースに認証情報を保持させていたり、その他外部のシステムと認証連携していてそちら側でも認証情報を破棄する必要があれば、別途処理を作ってあげる必要があります。

まとめ

というわけで今回はNextAuthを使ってNext.jsアプリケーションにメールアドレスとパスワードでのログイン機能を実装する方法をご紹介しました。

NextAuthは非常に便利な認証ライブラリで、GoogleやGithub、AppleやLINEなど外部システムと連携した認証機能実装も非常に簡単に行えるようになっていますので、ログイン機能実装におすすめのライブラリだと思います。