【Next.js】Auth.js・Prisma・MailHog・Dockerを用いたメールログイン機能の実装

# はじめに
今回は、Next.jsとAuth.jsを用いたメールログイン機能を実装したので、そちらを記事にしていこうと思います。

# 認証方法
認証方法は**マジックリンク**を用いています。
マジックリンクは、ログイン時に入力したメールアドレス宛にログイン用のリンクが届き、リンクからログインするという認証方法です。

# 必要なライブラリの導入
公式を参照し、以下を導入します。
- next-auth
- prisma
- @prisma/client
- @auth/prisma-adapter

```
yarn add next-auth @prisma/client @auth/prisma-adapter
yarn add prisma --dev
```

# 認証プロバイダーの実装
`[..nextauth].ts`を作成し、Providerを以下のように作成します。

```typescript:[...nextauth].ts
import { PrismaAdapter } from '@auth/prisma-adapter'
import NextAuth from 'next-auth'
import EmailProvider from 'next-auth/providers/email'

import { prisma } from '@/libs/prisma'

const EMAIL_SERVER = process.env.EMAIL_SERVER
const EMAIL_FROM = process.env.EMAIL_FROM
const NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET

if (!EMAIL_SERVER || !EMAIL_FROM || !NEXTAUTH_SECRET) {
  throw new Error('EMAIL_SERVER, EMAIL_FROM, or NEXTAUTH_SECRET not found.')
}

export default NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    EmailProvider({
      server: EMAIL_SERVER,
      from: EMAIL_FROM,
    }),
  ],
  secret: NEXTAUTH_SECRET,
})
```
記述はほとんど公式と同じようになっています。
Prismaを使用するので、adapterにPrisamAdapterを使用し、メール認証をするためEmailProviderを使用しています。
そのほかの認証を追加する場合、providersに追加していく形になります。
プロバイダーの実装は以上となります。

:::note info
API Routerについて補足
:::
ちなみに、Next.jsでは`API Route`といってapiディレクトリ以下にAPIのエンドポイントを作成することができます。例えば、デフォルトでは`hello.ts`というファイルがありますが、こちらには、`http://localhost:3000/hello`とすることでアクセスできます。

そのため`[..nextauth].ts`とすると、`/auth/[...nextauth]`となるため、`/auth/a`でも`/auth/b`でもアクセスできますし、`/auth/a/b`、`/auth/a/b/c`でもアクセスできます。要するに、ネストしたようなパスも全て、`[ファイル名].ts`でキャッチするということです。

Auth.jsではGoogle認証やTwitter認証などもあり、それをauthディレクトリ以下で管理するため、一元化するため（**/auth/google・/auth/twitterなどをまとめて管理するため**）、上記のようなファイル形式としています。

# prismaクライアントの実装
`src/libs`ディレクトリ以下に作成していきます。

```typescript:prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ['query'],
  })

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
```

公式とは少し記述が違いますが、このようにすることで正しく動作します。

# DB・MailHogの環境構築 with Docker
続いて、PrismaでのDB環境やMailHogを使用できるようにDockerでコンテナを作成していきます。

```yaml:docker-compose.yaml
version: '3.9'
services:
  db:
    image: mysql:8.0
    ports:
      - 3306:3306
    volumes:
      - mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=nextjs-prisma-graphql-codegen-dev
  mailhog:
    image: mailhog/mailhog
    ports:
      - 8025:8025
      - 1025:1025
volumes:
  mysql:
```

私の場合、MySQLを使用していますが、お好きなDBを使用してください。
Prismaのデフォルトがpostgresなので、postgresの方が構築は容易かもしれません。
（MySQLでの構築について記事にしているので、そちれも参照してみてください）
> [【Next.js】PrismaでMySQLローカル環境をDocker構築する方法](https://qiita.com/curry__30/items/94f055ef7fdde3f5b3c5)

また`mailhog`についても環境を構築するようにしています。
`http://localhost:8025`でアクセスできるようにしています。

次のコマンドを実行し、`.env`を作成します。
```
npx prisma init 
```
以下のようなファイルが作成されます。

```.env
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="mysql://root:パスワード@localhost:3306/DB名"
```

ここでコンテナによるローカル環境構築を行います。
```
docker-compose up
```

::: note alert
環境変数追加
:::

正常に立ち上がったら環境変数を追加していきます。
以下のようになります。

```.env
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="mysql://root:password@localhost:3306/nextjs-prisma-graphql-codegen-dev"
EMAIL_SERVER="smtp://user:password@localhost:1025"
EMAIL_FROM="noreply@example.com"
NEXTAUTH_SECRET="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
NEXTAUTH_URL="http://localhost:3000/"
```

`NEXTAUTH_SECRET`ですが、以下のコマンドでシークレット情報を取得し、コピーする必要があります。
```
openssl rand -base64 32
```

これで出力されたシークレットを貼り付けてください。

最後にPrismaのマイグレーションファイル（テーブル）を作成していきます。
以下を実行します。
```
npx prisma migrate dev
```

正常通りいけば、`prisma/migrations`にファイルが作成されます。

# フロントエンド実装
次に簡易的ではありますが、フロント部分を作成します。
まず、pagesディレクトリにある`_app.tsx`を改修します。

```typescript:_app.tsx
import '@/styles/globals.css'

import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <SessionProvider session={pageProps.session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}
```

ここでは、`SessionProvider`により、セッションを扱えるようにしています。

次に、`index.tsx`も手を加えます。
```typescript:index.tsx
import { signIn, signOut, useSession } from 'next-auth/react'

const Home = () => {
  const { data: session } = useSession()

  if (session) {
    return (
      <>
        Signed in as {session.user?.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    )
  }

  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  )
}

export default Home
```
こちらはAuth.jsの公式のまんまの記述になります。
`useSessioon`でセッションを扱え、セッション情報があれば、`user`情報を表示するようになっています。

::: note info
画面動作確認
:::

実際の画面の動きは以下です。
![スクリーンショット 2023-06-28 8.32.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/748d24cc-ba69-ad7a-2121-3368fb892018.png)
メールアドレスを入力し、ボタンを押すと、MailHogにメール通知が行きます。
MailHogには`http://localhost:8025`でアクセスできます。
![76b0c128e022edd608fa68379e232fd1_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/00a52d51-3d45-411c-e3c7-a86558b21c3a.gif)

さらに、以下のコマンドでPrismaスキーマのデータを確認できます。
```
npx prisma studio
```

`http://localhost:5555`でPrismaStudioを見ることができます。
Userスキーマですが、以下のようにデータが入っているのがわかると思います。
![スクリーンショット 2023-06-28 8.46.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/110d6945-293f-f19d-2480-9b72be931a8d.png)

以上で、実装は終了です。
参考にしてみてください。

# 参考文献
- [Auth.js](https://authjs.dev/getting-started/introduction)
- [API Route](https://nextjs.org/docs/pages/building-your-application/routing/api-routes)
- [【Next.js】PrismaでMySQLローカル環境をDocker構築する方法](https://qiita.com/curry__30/items/94f055ef7fdde3f5b3c5)