NextAuth.jsで認証機能実装

Next.js

2021.5.14

以前、Next.jsで開発を進める中で学んだIncremental Static Regenerationについて投稿しました。

今回は上記記事の続きとして、Next.jsでの認証機能実装についてまとめていこうと思います。

認証機能実装について

会員登録制のアプリケーションで必須となる認証機能。

認証機能はユーザーから提供された名前、メールアドレス、パスワードなどの資格情報を検証し、ユーザーが本人であるかを確認する重要で機密性の高い機能です。

これまで、個人開発や仮実装のアプリケーションではFirebaseを利用したメールアドレス、パスワードによる認証機能を実装していました。

Firebaseでももちろん実装できますが、Next.jsはFirebase以外にも複数の認証パターンをサポートしています。

まずNext.jsでの認証機能実装について、2つのパターンをみていきます。

Next.jsでの認証機能実装パターン

静的に生成されたページの認証

Next.jsはgetServerSidePropsの記載がない限りビルド時にページを静的生成(SSG:Static Site Generation)します。

ビルド時にはユーザー情報はないので、ユーザー情報の取得は必然的にCSR(Client Side Rendering)になります。

例えば認証済みのユーザーでないとホーム画面へリダイレクトするようなページの場合、SSGで既にページ(具体的にはユーザー情報表示の枠組み部分)が作成されており、CSRでユーザー情報について取得後にホーム画面へリダイレクトされます(その過程で、一瞬ページのコンテンツが表示(フラッシュ)されるという注意点があります)。

サーバーでレンダリングされるページの認証

クライアント側からリクエストがあってサーバーでページを生成するSSR(Server Side Rendering)では、getServerSideProps関数内でユーザー情報をAPIで取得する処理を実装します。

getServerSideProps関数内の処理が終わるまでページはレンダリングされないので、SSGのリダイレクト前のコンテンツのフラッシュを防ぐことができます。しかしその一方、ユーザー情報取得処理を高速に行う必要があります。

Next.js公式ドキュメントでも、ユーザー情報取得処理を高速化できない場合はSSGを検討するようにとする記載があります。

NextAuth.jsという選択

ではSSG+CSRでどのように認証機能を実装するか?

NextAuth.jsというNext.js用のオープンソースの認証機能ライブラリがあります。

こちらはデータベースの有無に関わらず、GoogleやTwitterなどのソーシャルログインやメールリンク認証など、あらゆるパスワードレス認証機能に対応しています。

パスワードレス認証とはその名のとおり「パスワードがない」認証です。

これまでのID(メールアドレス)とパスワードを使用した認証機能には、

  • セキュリティの課題(パスワードリスト攻撃:攻撃者が入手したID・パスワードのリストにより正規ルートから不正アクセスを試みるサーバー攻撃の)

  • コスト(パスワード紛失)

の潜在的な課題がありました。

パスワードレス認証は、「パスワードがない」ことで、上記課題を根本から解決することができます。

NextAuth.jsはユーザーの機密情報(パスワード!)を保存する必要がないように構築されており、簡単にNext.jsプロジェクトへパスワードレス認証を導入することができます。

今回のプロジェクトでは、セキュリティ面もふまえてNextAuth.jsを採用することにしました。

認証機能実装

それでは早速NextAuth.jsで認証機能を実装していきます。

1. API実装

プロジェクトにNextAuth.jsをインストールし、pages/api/authディレクトリ内に**[...nextauth].js**ファイルを作成します。

import NextAuth from 'next-auth' import Providers from 'next-auth/providers' export default NextAuth({ // Configure one or more authentication providers providers: [ Providers.GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }), // ...add more providers here ], // A database is optional, but required to persist accounts in a database database: process.env.DATABASE_URL, })

NextAuth.js公式Docより引用

上記コードはGithubログイン実装の例です。

GITHUB_IDGITHUB_SECRETはGithubのDeveloper settingsから取得して、.env.localファイルに保存します。(GithubでのAuthorization callback URLはアプリURL/api/authとします。)

また、.env.localファイルにはNEXTAUTH_URLの環境変数も追加します。今回の例ではローカルなのでhttp://localhost:3000/api/authを記述しました。

上記コードのprovidersの部分を変えることで、さまざまなソーシャルログインの実装ができます。(もちろん複数も実装可能です。)

今回の例ではデータベース連携は実装しませんが、databaseの部分にデータベースの情報を記述することで、簡単に連携ができます。

2. フロントエンド実装

フロントエンド部分にはnext-auth/clientからsignIn, signOut, useSessionをインポートしてログインボタン/ログアウトボタンを実装します。

下記の例ではログイン/ログアウトボタンの表示をsessionによって切り替えています。

import { signIn, signOut, useSession } from "next-auth/client"; export default function Home() { const [session, loading] = useSession(); return ( <> {!session && ( <> サインインしてください。 <br /> <button onClick={()=>signIn}>Sign in</button> </> )} {session && ( <> サインイン完了。 email: {session.user.email} <br /> <button onClick={()=>signOut}>Sign out</button> </> )} </> ); }

signInは何も指定しなければ、後述の簡易的なログインページにリダイレクトするメソッドです。signOutは名前のとおりログアウトです。

重要なのがuseSession hookです。

NextAuth.jsはsession をログして見ると、アクセストークンやユーザー情報が入っているのが分かります。

NextAuth.js公式Docより引用

ログインしていない場合sessionはnullになるので、sessionによりボタン表示を切り替えることができます。

さらに_app.tsxで next-auth/clientからインポートした<Provider /> でラップし、page間でsession情報を共有できるようにします。

import { Provider } from "next-auth/client"; export default function App({ Component, pageProps }) { return ( <Provider session={pageProps.session}> <Component {...pageProps} /> </Provider> ); }

ここまででdevサーバーを起動し、ログインボタンをクリックすると、NextAuth.js が指定したプロバイダでログインページを作ってくれているのが分かります。

prisma2

とても少ない記述量で認証機能が実装できました!

上記の例ではデータベース連携は実装していませんが、NextAuth.jsではMySQL、MariaDB、PostgreSQL / CockroachDB等さまざまなデータベースとの連携がサポートされています(公式ドキュメントに[...nextauth].jsでの各データベース連携のサンプルコードがあります)。

また、サインインページも自動的に作成されるページではなく、自分で作成したページを使用することができます。

認証されたユーザーの制御

ではログイン後のユーザーをどのように制御するか、具体的な事例でみていきます。

先ほどログイン/ログアウトボタン表示切り替えの例を挙げましたが、仕組みは一緒で、useSession hookを使います。

例えばユーザーのプロフィールページの場合、プロフィールページへのリンクはログイン前には表示させないようにします。

さらに、プロフィールページの方で、ログインしていないユーザーがプロフィールページへアクセスした場合は"/"ページにリダイレクトするようにします。

import { useRouter } from "next/router"; import { useSession } from "next-auth/client"; import Link from "next/link"; export default function MyPage() { const router = useRouter(); const [session, loading] = useSession(); if (loading) { return <div>Loading....</div>; } if (!session) { router.push("/"); } return ( <div> MyPage <p>{session.user.name}</p> <img src={session.user.image} /> <Link href="/">HOMEにもどる</Link> </div> ); }

ちなみにローディング中はLoading....と表示するようにしています。

簡単な例ですが、こちらも少ない記述量でユーザーの制御をすることができました!

まとめ

NextAuth.jsを利用することで、とても簡単に認証機能を実装することができました。

上記ははじめの第一歩として簡単な例ですが、次回はデータベース連携についてまとめられたらと思っています。

それでは、また次回です!

Megumi Tanimoto

Editor

フロントエンドエンジニア1年目。
エンジニアライフ、技術的なアウトプットを中心に発信します。