最近next.jsでいろいろアプリを作っているのですが、そのときにnextauthというライブラリで認証機能をさくっと作れることを知りまして、作ってみようと思いました。
自分のGithubのレポジトリはこちらです。
やりたいこと
- Next.jsでウェブアプリを作る
- Githubで認証できるようにする
- データベースにログイン情報を保存できるようにする
- すべて無料サービスで行う
使う技術構成
- Next.js
- Typescript
- nextauth
- mysql
- Docker
- Prisma
使うサービス
- Github App
- Vercel
- Heroku ClearDB
ファイル構成
tree -I node_modules
.
├── README.md
├── app
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ │ ├── access-denied.tsx
│ │ ├── footer.module.css
│ │ ├── footer.tsx
│ │ ├── header.module.css
│ │ ├── header.tsx
│ │ └── layout.tsx
│ ├── global.d.ts
│ ├── next-env.d.ts
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── api
│ │ │ ├── auth
│ │ │ │ └── [...nextauth].ts
│ │ │ └── examples
│ │ │ ├── jwt.ts
│ │ │ ├── protected.ts
│ │ │ └── session.ts
│ │ ├── api-example.tsx
│ │ ├── client.tsx
│ │ ├── index.tsx
│ │ ├── policy.tsx
│ │ ├── protected.tsx
│ │ ├── server.tsx
│ │ └── styles.css
│ ├── prisma
│ │ ├── migrations
│ │ │ ├── 20210521153533_20210521
│ │ │ │ └── migration.sql
│ │ │ └── migration_lock.toml
│ │ └── schema.prisma
│ ├── tsconfig.json
│ ├── types
│ │ ├── environment.d.ts
│ │ ├── next-auth.d.ts
│ │ └── next.d.ts
│ └── yarn.lock
├── docker-compose.yml
└── mysql
├── Dockerfile
└── my.cnf
フォルダ作成
typescriptバージョンのnext-authのサンプルを使って修正していきます。
こちらのテンプレートをそのまま使わせていただきます。
自分はjamstack app
と名付けました。
$ mkdir jamstack
$ cd jamstack
$ mkdir mysql
$ git clone git@github.com:nextauthjs/next-auth-typescript-example.git app
app 内のgitの紐付けも外し、yarn installでパッケージをインストールしておきます。
$ cd app
$ rm -rf .git
$ yarn install
Prismaのインストール
Prismaをインストールして初期化します。
$ yarn add prisma --dev
$ yarn add @prisma/client
$ npx prisma init
これで.env
ファイルが生成されるので、ここの環境変数を編集します。
基本的にコンテナの中からアクセスするので、mysqlコンテナの3306ポートを指定します。
(外側からの場合は 127.0.0.1:13306
となります。)
DATABASE_URL="mysql://root:root@mysql:3306/jamstack_db"
docker-compose.ymlの作成
ルートディレクトリに docker-compose.yml
を置きます。
mysqlとnext.js用のコンテナを2つ用意します。
version: '3'
services:
next:
image: node:15.0.1
volumes:
- ./app:/home/app
- node_modules_volume:/home/app/node_modules
ports:
- 3000:3000
working_dir: /home/app
command: [bash, -c, yarn upgrade --no-progress --network-timeout 1000000 && yarn run dev]
mysql:
build: ./mysql
environment:
TZ: Asia/Tokyo
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: jamstack_db
ports:
- 13306:3306
volumes:
- mysql_volume:/var/lib/mysql
volumes:
mysql_volume:
node_modules_volume:
mysqlの設定ファイルは以下です。
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character_set_server=utf8mb4
collation_server=utf8mb4_bin
# タイムゾーンの設定
default_time_zone=SYSTEM
log_timestamps=SYSTEM
# デフォルト認証プラグインの設定
default_authentication_plugin=mysql_native_password
# mysqlオプションの設定
[mysql]
# 文字コードの設定
default_character_set=utf8mb4
# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default_character_set=utf8mb4
mysqlのDockerfileです。
FROM mysql:8.0.21
# FROM mysql@sha256:77b7e09c906615c1bb59b2e9d7703f728b1186a5a70e547ce2f1079ef4c1c5ca
RUN echo "USE mysql;" > /docker-entrypoint-initdb.d/timezones.sql && mysql_tzinfo_to_sql /usr/share/zoneinfo >> /docker-entrypoint-initdb.d/timezones.sql
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
Githubの認証の設定
今回はGithubアカウントを使って認証をしたいので、Github Appを作ることになります。Settingsを開きます。
下までスクロールして Developer settingsをクリックします。
ここで New Github Appを作ります。
appの名前はjamstack-app
として、homepage URLはとりあえずgithubのレポジトリのリンクにしておきます。
Callback URL
はhttp://localhost:3000
としておきます。Vercelなどでデプロイしたらまた変更します。
これで Create App
とします。すると次の画面に到達するので、client secretをgenerateします。client secret
とClient ID
は保存しておきます。
環境変数の設定
クローンしたレポジトリに.env.local.example
があるはずなので、内容をコピーして .env
に追加します。先ほどゲットした変数もペーストしておきます。
NEXTAUTH_URL=http://localhost:3000
GITHUB_ID=[Client ID]
GITHUB_SECRET=[Client secret]
prisma周辺の設定
prismaをインポートするように[...nextauth].ts
を編集します。
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
// 以下を追加
import { PrismaClient } from "@prisma/client";
import Adapters from "next-auth/adapters";
let prisma;
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
} else {
if (!global.prisma) {
global.prisma = new PrismaClient();
}
prisma = global.prisma;
}
...
// adapterを追加
database: process.env.DATABASE_URL,
adapter: Adapters.Prisma.Adapter({ prisma }),
このままだとtypescriptに怒られるので、global.d.tsにprismaをanyとして定義しておきます。(もっといい方法を探す余裕が今回ありませんでした、すみません。)
export {};
declare global {
namespace NodeJS {
interface Global {
prisma: any;
}
}
}
マイグレーションする
マイグレーションするときにPrismaのスキーマファイルをここに書きます。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Account {
id Int @id @default(autoincrement())
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 @id @default(autoincrement())
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 @id @default(autoincrement())
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 @id @default(autoincrement())
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")
}
マイグレーションを行う際には、コンテナに入って行いました。
$ dx jamstack_next_1 bash
root@ea23ae917a29:/home/app# npx prisma migrate dev
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": MySQL database "jamstack_db" at "127.0.0.1:13306"
✔ Enter a name for the new migration: … 20210521
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20210521153533_20210521/
└─ migration.sql
Your database is now in sync with your schema.
✔ Generated Prisma Client (2.23.0) to ./node_modules/@prisma/client in 158ms
これでSequel Proなどで確認してみると、dbが作成されているのがわかります。
立ち上げてみる
$ docker-compose up
localhost:3000にて、You are not signed in
の状態でアクセスできるはずです。
これでSign inボタンを押すと、いろいろ選択肢が出てくるはずです。今回はGithubの設定しかしていないので、Sign in with Github
を選びます。
そうすると無事ログインできているはずです。
デプロイ
デプロイするときには二つのサービスを使います。
- Heroku ClearDB (ユーザー、セッションなどの管理)
- Vercel (Next.jsのアプリをデプロイする)
HerokuでのDBのデプロイ
herokuで適当なアプリを作って、そこにcleardbのアドオンを追加します。そこで出てきたCLEARDB_DATABASE_URL
をメモっておいてください。以下の5行でとりあえず終わりです。
$ heroku login
$ heroku git:remote --app database-provisioning
$ heroku apps:create database-provisioning
$ heroku addons:create cleardb:ignite
$ heroku config
CLEARDB_DATABASE_URL=......
Vercelでのデプロイ
Vercelで最後にデプロイします。このときにpackage.json
を修正して、マイグレーションを行う設定を追加します。あと、npm-run-all
を走らせる設定も追加します。
{
...........
"homepage": "http://next-auth-typescript-example.now.sh",
"main": "",
"scripts": {
// ココ変更!
"migrate:deploy": "prisma migrate deploy",
"dev": "next",
"build": "npm-run-all migrate:deploy build-app",
"build-app": "next build",
"start": "next start",
"types": "tsc --noEmit"
},
...........
// npm-run-all を走らせる
"devDependencies": {
"@types/node": "^14.14.41",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
"prisma": "^2.23.0",
"npm-run-all": "^4.1.5",
"typescript": "^4.2.4"
}
}
Vercelでデプロイする際には、DATABASE_URL
の環境変数を追加しましょう。
vercelでもデプロイできています!
01:38:57.342 $ npm-run-all migrate:deploy build-app
01:38:57.810 $ prisma migrate deploy
01:38:58.325 Prisma schema loaded from prisma/schema.prisma
01:38:58.358 Datasource "db": MySQL database "heroku_819789418405824" at "us-cdbr-east-03.cleardb.com:3306"
01:39:00.084 1 migration found in prisma/migrations
01:39:02.142 The following migration have been applied:
01:39:02.143 migrations/
01:39:02.143 └─ 20210521153533_20210521/
01:39:02.143 └─ migration.sql
01:39:02.143
01:39:02.143 All migrations have been successfully applied.
01:39:02.619 $ next build
環境変数の追加
先ほどは追加し忘れた環境変数(NEXTAUTH_URL
など)も追加しておきます。
NEXTAUTH_URLはcanonical urlを貼るようにしてください。以下だと jamstack-greenteabiscuit.vercel...
となります。
GithubのCallback URLも更新しておきます。ローカルで開発するときとで分けないといけないのがちょっと面倒です。ローカル用のGithub Appと本番用のGithub Appで分けてもいいかもしれません。
参考
以下が参考になりました。ありがとうございました。
Next.js + Prisma + NextAuth.js + React Query で作るフルスタックアプリケーションの新時代