✉️

NextAuth.js + Firestore + smtp4devでメールアドレス認証を最速実装

2023/08/11に公開

前提

現時点で NextAuth.jsAuth.js への置き換えが進行中です。
その影響だと思われますが、ドキュメント通り実装を進めても一部うまく動作しない部分があったため、Auth.js の @auth/firebase-adapter は利用しておりません。
また、そのバージョンを明確にするため、この記事執筆時点でのライブラリのバージョンを詳細に記載しています。

経緯

とあるプロジェクトのとある機能を実装する上で、NextAuth.js のメールアドレス認証を使って検証したい内容がありました。
機能実装に向けての検証であったため、とにかくクイックにメールアドレス認証できる環境を構築して検証に入りたかったのですが、なかなか「これさえ見ればOK」というような記事が見当たらなかったため、とにかく最速でメールアドレス認証を実装する方法を書き残しておきます。

利用した主要なライブラリとバージョン

  • next@13.4.12
  • next-auth@4.22.3
  • @next-auth/firebase-adapter@2.0.1
  • firebase-admin@11.10.1

利用したツール

  • yarn
  • smtp4dev

NextAuth.js のテンプレートを利用してプロジェクト作成

まずは NextAuth 公式が提供してくれている、リポジトリテンプレートを利用してプロジェクトを作成します。
https://github.com/nextauthjs/next-auth-example

作成したプロジェクトをローカルにクローンし、yarnで依存するパッケージをインストールしておきます。
ここでは、テンプレートを利用してnext-auth-sandboxというリポジトリを作成した、という前提で記載します。

git clone <テンプレートを利用して作成したリポジトリ>
cd next-auth-sandbox/
yarn

また、テンプレートには最低限必要なパッケージがすでにインストールされていますが、メールアドレス認証やFirestoreとの連携に必要なパッケージはインストールされていません。
なので、ここで必要なパッケージをインストールしておきます。

yarn add nodemailer @next-auth/firebase-adapter firebase-admin

Firestore との連携設定

Firebase プロジェクトの作成

Firebaseプロジェクトを初めて作成される方は、Firebase Consoleで「プロジェクトを作成」、過去に作成したことがある方は「プロジェクトを追加」をクリックします。

プロジェクトの作成手順については、Firebase側でかなり親切なインストラクションがあるので、ここでは細かい手順は割愛します。
手順の中にGoogleアナリティクスを有効にするかどうかの選択がありますが、Firestoreとの連携には不要なのでどちらでもOKです。

Firestore データベースの作成

続けて、Firestore データベースを作成します。
左ペインの Firestore Database をクリックすると「データベースの作成」ボタンが表示されるのでクリックします。

すると、データベース作成ヘルパーが表示されます。
セキュリティ保護ルールについては「テストモードで開始する」を選択。

Cloud Firestore のロケーション選択は、今回は asia-northeast1 (Tokyo) を選択。
設定後に変更できない点にだけ注意してください。

ロケーションを選択して「有効にする」をクリックすると Firestore データベースが作成されます。

作成した Firebase プロジェクトを利用するアプリを登録

ここまでで作成した Firebase プロジェクトと、next-auth を連携するためのファイルを準備します。
左ペインの プロジェクトの概要 をクリックしてトップに戻り、「アプリに Firebase を追加して利用を開始しましょう」の下の赤枠のボタンをクリックします。
少々分かりづらいですが、Webアプリと Firebase プロジェクトを連携するためのボタンです。

Firebase 上で連携先のアプリが分かりやすいようにニックネームを設定します。
「このアプリの Firebase Hosting も設定します。」のチェックボックスは、今回の設定には不要なため外したままにしておきます。

「アプリを登録」ボタンをクリックすると、次のステップとして「Firebase SDK の追加」が開きますが、今まさに追加している最中なので、無視して「コンソールに進む」をクリックします。

コンソールに戻ったら、左ペイン「プロジェクトの概要」の右側にある設定アイコンから「プロジェクトの設定」をクリックし、「サービスアカウント」タブを開きます。

タブ内の「新しい秘密鍵を生成」ボタンをクリックし、その後「キーを生成」ボタンをクリック。
Firebase Admin SDK を利用可能にするためのJSONファイルがダウンロードされます。

ダウンロードしたJSONファイルをプロジェクトのルートディレクトリに移動しておきます。また、このタイミングでJSONファイルのリネームもしておきます。
すぐに.gitignoreにも追記して、gitの管理対象外に置きましょう。

mv ~/Downloads/<ダウンロードしたJSONファイル> /path/to/next-auth-sandbox/serviceAccount.json
.gitignore
.DS_Store

node_modules/
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.yarn-integrity
.npm

.eslintcache

*.tsbuildinfo
next-env.d.ts

.next
.vercel
.env*.local
+ serviceAccount.json

環境変数の設定

テンプレートプロジェクトには.env.local.exampleという環境変数設定ファイルのサンプルが用意されています。
これをコピぺして、自分の環境に合わせた環境変数を設定します。

cp .env.local.example .env.local

デフォルトで存在するAUTH0_IDからTWITTER_SECRETの環境変数は削除してもしなくてもどちらでも良いです。追加する環境変数は下記のとおりです。

  • EMAIL_SERVER
    • SMTPサーバの認証情報とアドレスを設定します。後ほど書きますが、ここではsmtp4devを利用するので認証情報は空でOK
  • EMAIL_FROM
    • どんなアドレスからメールが届くようにするかを設定します。仮でnoreply@example.comとしています。
  • GOOGLE_APPLICATION_CREDENTIALS
    • 先ほど作成した Firebase Admin SDK の認証情報ファイルへのパスを設定します。これでFirebase Admin SDK が自動で認証情報を読み込んでくれます。
.env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32


-AUTH0_ID=
-AUTH0_SECRET=
-AUTH0_ISSUER=
-
-DESCOPE_ID=
-DESCOPE_SECRET=
-
-FACEBOOK_ID=
-FACEBOOK_SECRET=
-
-GITHUB_ID=
-GITHUB_SECRET=
-
-GOOGLE_ID=
-GOOGLE_SECRET=
-
-TWITTER_ID=
-TWITTER_SECRET=
-
+EMAIL_SERVER=smtp://"":""@localhost:2525
+EMAIL_FROM=noreply@example.com
+
+GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json
+

実装

これでようやく設定が完了したのでコードを修正していきます。一瞬です。
テンプレートのコードを修正してメール認証、Firestoreとの連携部分を設定します。

pages/api/auth/[...nextauth].ts
 import NextAuth, { NextAuthOptions } from "next-auth"
- import GoogleProvider from "next-auth/providers/google"
- import FacebookProvider from "next-auth/providers/facebook"
- import GithubProvider from "next-auth/providers/github"
- import TwitterProvider from "next-auth/providers/twitter"
- import Auth0Provider from "next-auth/providers/auth0"
+ import EmailProvider from "next-auth/providers/email"
+ import { FirestoreAdapter } from "@next-auth/firebase-adapter";
 
 // For more information on each option (and a full list of options) go to
 // https://next-auth.js.org/configuration/options
 export const authOptions: NextAuthOptions = {
   // https://next-auth.js.org/configuration/providers/oauth
+  adapter: FirestoreAdapter(),
   providers: [
-    Auth0Provider({
-      clientId: process.env.AUTH0_ID,
-      clientSecret: process.env.AUTH0_SECRET,
-      issuer: process.env.AUTH0_ISSUER,
-    }),
-    FacebookProvider({
-      clientId: process.env.FACEBOOK_ID,
-      clientSecret: process.env.FACEBOOK_SECRET,
-    }),
-    GithubProvider({
-      clientId: process.env.GITHUB_ID,
-      clientSecret: process.env.GITHUB_SECRET,
-    }),
-    GoogleProvider({
-      clientId: process.env.GOOGLE_ID,
-      clientSecret: process.env.GOOGLE_SECRET,
-    }),
-    TwitterProvider({
-      clientId: process.env.TWITTER_ID,
-      clientSecret: process.env.TWITTER_SECRET,
-      version: "2.0",
-    }),
+    EmailProvider({
+      server: process.env.EMAIL_SERVER,
+      from: process.env.EMAIL_FROM
+    }),
   ],
   callbacks: {
     async jwt({ token }) {
       token.userRole = "admin"
       return token
     },
   },
 }
 
 export default NextAuth(authOptions)

動作確認

smtp4devの起動

SMTPサーバがないとメールが送られたことの確認ができないので、ローカルにダミーのSMTPサーバを立てます。
dockerが利用できる場合は、dockerでsmtp4devを起動するのが一番簡単で速いです。
next.jsが3000番ポートで起動するので、smtp4devは3001番ポートでアクセスできるようにします。

docker run --rm -it -p 3001:80 -p 2525:25 rnwood/smtp4dev

next-auth サンプルアプリケーションの起動

別ターミナルを開き、yarn devでサンプルアプリケーションを起動します。

「Sign in」ボタンをクリックすると、メールログイン画面に遷移します。
メールアドレスを入力し、「Sign in with Email」ボタンをクリックします。
この時、メールが実際に送信されるわけではないのでアドレスは仮のものでもOKです。

すると、メールをチェックしてねという画面に切り替わります。

どこにメールが送信されているかというと、smtp4devで送信されたメールが確認できます。
http://localhost:3001にアクセスするとsmtp4devで受信ボックスのようなUIが構築されており、1件メールが届いていることが分かると思います。
これが next-auth からの認証メールです。

メール内の「Sign in」ボタンをクリックすると next-auth のサンプルアプリケーションに戻り、先ほど入力したメールアドレスのユーザとして認証されている旨の表示がされます。

参考にさせていただいたサイト

https://authjs.dev/reference/adapter/firebase

https://next-auth.js.org/configuration/providers/email

Discussion