見出し画像

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

こんにちは、フロントエンドエンジニアの谷本です。

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

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

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

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

データベースとの連携

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

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

ORM(Object-relational mapping)とは

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

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

添田さんが前回の記事で「Django ORM (Django Object-Relation Mapping)」についてまとめていましたが、そのNode.js/TypeScript版です。

憧れのデータマスターへの道!Prismaもその一歩!

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

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

スクリーンショット 2021-05-22 16.20.12

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

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

スクリーンショット 2021-05-22 16.24.40

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

スクリーンショット 2021-05-22 16.24.49

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

スクリーンショット 2021-05-22 16.28.32

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

ちなみに、前回認証済みのユーザーについては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生成(更新)時の処理を行うことができます。(参考:https://next-auth.js.org/configuration/callbacks)


まとめ

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

では、また次回です!

この記事が気に入ったらサポートをしてみませんか?