NextAuth.jsで認証機能実装その2【データベース連携】

Next.js

2021.5.24

前回、NextAuth.jsでパスワードレス認証が簡単に実装できる!という記事を書きました。

今回はその第二弾として、データベース連携についてまとめていきたいと思います。

👑ゴール:認証したユーザーをデータベースに保存

今回のゴールは、前回実装したGithubログインしたユーザーの情報をデータベースに保存することです。前回に引き続き、NextAuth.jsを使用して実装します。

データベースとの連携

前回の記事でも書きましたが、NextAuth.jsではMySQL、MariaDB、PostgreSQL / CockroachDB等のデータベース連携がサポートされています。

今回の例ではPostgreSQLを使用します。また、ORMとしてPrismaを使用します。

ORM(Object-relational mapping)とは

Prismaは、オープンソースのORMです。

ORMとは、簡単に言うとアプリケーション(オブジェクト指向プログラミング言語)とデータベース(リレーショナルデータベース)の間に入ってデータを変換し繋いでくれるものです。

Prismaによって、データベース言語(SQL)を書くことなく、Node上のアプリケーションで直接データベースに接続し、クエリ発行が可能になります。

今回の例でいうと、直接会話すると言葉が通じないところがあるNext.js(で作ったアプリケーション)とPostgreSQL(データベース管理システム)の間に入って両者のやりとりがスムーズになるよういい具合に通訳して仲を取り持ってくれる仲人さんがORMということですね。

NextAuth.jsもPrismaを利用したデータベース連携をサポートしており、サンプルコードも掲載されています。

Prismaの構造

実装の前にPrismaの構造を簡単にご紹介します。

Prisma Model

アプリケーションで使用するモデルを表現する。 モデル内でテーブルやカラムの定義を行う。 また、Prisma Clientでクエリを発行するために必要となる。

Prisma schema

メインとなる設定ファイル。 データベースクライアントやマイグレーションファイルの生成を行う。

Prisma Client

データベースクライアント。 Prisma Modelでの型情報を使用して、クエリーの結果は型安全になる。

Prisma Migrate

Prisma Schemaの情報をベースにマイグレーション周りの処理が可能になる。

Prisma Studio

データベース上のデータを閲覧・編集するGUI。

ORM初心者にとってありがたいのはPrisma Studioの存在。詳しくは後述しますが、データをブラウザで閲覧・操作できるのでわかりやすい!Prismaは開発者の生産性向上を目的に開発されているそうです。

実装

1.Prismaインストール

npm install prisma --save-dev npm install @prisma/client

インストールしたら、

npx prisma init

コマンド実行することで、rootディレクトリにprismaディレクトリと.envファイルが作成されます。

2.Prisma schemaファイルの作成

NextAuth.jsで認証したユーザー情報を保存するためのスキーマファイルを作成します。公式ドキュメントにスキーマファイルのサンプルがありますので、今回はこちらを利用します。

generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = "file:./dev.db" } model Account { id Int @default(autoincrement()) @id compoundId String @unique @map(name: "compound_id") userId Int @map(name: "user_id") providerType String @map(name: "provider_type") providerId String @map(name: "provider_id") providerAccountId String @map(name: "provider_account_id") refreshToken String? @map(name: "refresh_token") accessToken String? @map(name: "access_token") accessTokenExpires DateTime? @map(name: "access_token_expires") createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @default(now()) @map(name: "updated_at") @@index([providerAccountId], name: "providerAccountId") @@index([providerId], name: "providerId") @@index([userId], name: "userId") @@map(name: "accounts") } model Session { id Int @default(autoincrement()) @id userId Int @map(name: "user_id") expires DateTime sessionToken String @unique @map(name: "session_token") accessToken String @unique @map(name: "access_token") createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @default(now()) @map(name: "updated_at") @@map(name: "sessions") } model User { id Int @default(autoincrement()) @id name String? email String? @unique emailVerified DateTime? @map(name: "email_verified") image String? createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @default(now()) @map(name: "updated_at") @@map(name: "users") } model VerificationRequest { id Int @default(autoincrement()) @id identifier String token String @unique expires DateTime createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @default(now()) @map(name: "updated_at") @@map(name: "verification_requests") }

3.Prisma Clientの生成とマイグレーション

schemaファイルを作成したら、

npx prisma generate

コマンドを実行します。

これにより下記のとおりPrisma Clientコードがnode_modules/@prisma/clientに生成され、ここからimportすることでPrisma Clientインスタンスを作成することができます。

✔ Generated Prisma Client (2.23.0) to ./node_modules/@prisma/client in 103ms You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient()

また、schemaファイルをもとにmigrateを行います。(開発環境のためmigrate devコマンドを使用します。)

npx prisma migrate dev --name init

prismaディレクトリ内にmigrationsディレクトリが生成され、migrateの履歴を見ることができます。

4.[...nextauth].jsファイルにAdapterの記述追加

import NextAuth from "next-auth" import Providers from "next-auth/providers" import Adapters from "next-auth/adapters" import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() export default NextAuth({ providers: [ Providers.GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }), ], adapter: Adapters.Prisma.Adapter({ prisma }), // 追加 })

これにより、NextAuth.js が prisma と連携して、サインインなどのアクションがあった場合、prisma 経由でユーザー情報をデータベースに保存することができます。

5.Prisma studioでデータ生成の確認

ここまででNextAuth.jsとPrismaによるデータベース連携の実装は完了です。

ログインしたユーザーがデータベースに保存されたか確認するために、Prismaの構造で紹介したPrisma Studioを起動します。

npx prisma studio

ブラウザで確認すると、マイグレーションしたモデルが表示されています。 prisma1

schema.prismaファイルに記載したとおり、Account,Session,User,VerificationRequestモデルが生成できていることがわかります!データベースの中身をブラウザで確認できるPrisma Studio、ありがたいです・・・

ここでブラウザで開発モードを立ち上げ、前回実装したGithubログインをしてみます。 prisma2

NextAuth.jsのsigninメソッドを使用することで上記のように自動的に生成されるシンプルなログイン画面が表示されます。こちらは自分で作成した別のログインページに置き換えることができます。 prisma3

サインイン完了!Prisma Studioを確認してみると・・・・ prisma4

データが追加されています!

ちなみに、前回認証済みのユーザーについてはsessionによって挙動をコントロールすることができるという話をしました。

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

認証済み(sessionがある)かどうかでSign inボタンとSign outボタンの表示/非表示を切り替えています。

sessionにはユーザー情報やアクセストークンが入っています。

認証済みユーザーの情報については、session.userで取得することができますが、ここで取得できる情報はProviderの情報(今回の例だとmail,name,image)になるので、追加でidなどデータベースの情報を取得したい場合は[...nextauth].jsファイルのoptionにcallbacksとして追記することで取得できます。

const options = { providers: [ Providers.GitHub({ clientId: process.env.GITHUB_ID || "", clientSecret: process.env.GITHUB_SECRET || "", }), ], adapter: Adapters.Prisma.Adapter({ prisma }), callbacks: { // 追加 session: async (session, user) => {  session.user.id = user.id; return Promise.resolve(session); }, }, };

callbacksはこの他にもサインイン実行時に非同期で実行される処理やリダイレクト処理、JSON Web Token生成(更新)時の処理を行うことができます。(参考:公式Doc

まとめ

業務を通してPrismaを初めて使用しましたが、ORMとは何かというところも含めて勉強になりました。認証機能にも様々な実装パターンがあり、Prismaのように新しいサービスも生まれるので、日々勉強ですね。

では、また次回です!

Megumi Tanimoto

Editor

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