NextAuth.jsを用いた認可アプリケーション
内容
NextAuth.jsを使って認可に基づく認証機能を実装するという話です. コードは基本的にNextAuth.js公式のExampleを使っているだけです. Next.jsが分かるという人は直接そちらを見たほうがわかりやすいかと思います. Next.js全然分からないけどNexAuth.jsどんなのか知りたいという人には良いかもしれません.
モチベーション
Next.jsにはサーバーも統合されておりAPIを作成する機能も存在します. これを使うと認可機能とかも作れるのかなぁと思っていたのですが, NetxAuth.jsというのがすでにありました. 単なる認可機能を提供するだけではなくNetx.jsの機能を非常に上手く使っているなぁと感心したので勉強がてら使ってみました. このプロジェクト自体はVercelやNetx.jsプロジェクトとは無関係のようですがReactやNext.jsの使い方としてとても勉強になりそうで, そのへんを意識して記事を書いていきたいです.
作るもの
- GitHub OAuth Appを使った認可アプリケーション
要はGitHubでログインという機能を作りたいということです. 認可できたらGitHub APIを使ってリポジトリの操作とかもしていきたいですね.
GitHub OAuthの流れ
予めGitHub OAuth Appを作っておく必要があります.
- 認可ページ(
https://github.com/login/oauth/authorize
)へのリダイレクト - ユーザーによる認可の実行
- Callback URLへの遷移
- アクセス・トークンの取得(
https://github.com/login/oauth/access_token
) - トークンを用いAPIへのアクセス
というのが大体の流れです. この際client idとclient secretというOAuthアプリごとに生成される固有の値が必要になります.
プロジェクの作成とNextAuth.jsの導入
create-next-app github-auth --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter" && \
cd github-auth && \
npm i next-auth
プロジェクトのファイル構成は以下のようになります.
.
├── node_modules
├── package.json
├── package-lock.json
├── pages
├── public
└── README.md
pagesがアプリのページ(ルート)に対応するファイルが入ります. 現在はindex.jsというファイルだけが入っています.
pages
└── index.js
サーバー側(API)
Next.jsでのAPIエンドポイントの作り方
まずAPIルートの定義の仕方を簡単に説明します.
通常pagesフォルダ以下は各ページに対応するファイルが入っています. しかしpages/apiフォルダ以下はAPIとして処理されます. 例えばpages/api/user.jsはapi/userというパスに対応します.
固定的なリソース名の場合はuser.jsのように特定の名前のファイルを作成すれば良いのですが, idのような様々な値がパスとして与えられる場合はどうするのでしょうか? このようなパス名を動的に生成する場合, 例えばuser/123のようなパスを作成したい場合はpages/api/user/[pid].jsのようにします.
キャッチ・オールAPIルート
更にpages/api/post/[...slug].jsのように三点リーダが加わるとキャッチ・オールAPIと呼ばれpost以下のパスは任意のパスが認識されます. post/aでもpost/a/bでもpost/a/b/cでも良いようになります.
NextAuth.jsではpages/api/auth/[...nextauth].jsに認可に関する記述を行います. これはキャッチ・オールAPIなのでauth以下のパスへの要求はパスの構成によらずNextAuth.jsが自動的に処理してくれるようです.
All requests to /api/auth/* (signin, callback, signout, etc) will automatically be handed by NextAuth.js.
認可ルート
/api/auth/以下へのリクエストはpages/api/auth/[...nextauth].jsに定義されたリクエスト・ハンドラで処理されることになる. リクエスト・ハンドラとはNextApiRequestとNextApiResponseを引数に取る関数です.
export default (req, res) => {
// レクエストに対して何かする
}
NextAuthではNextAuthという関数を返すだけになっています.
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
const options = {
// Configure one or more authentication providers
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}),
// ...add more providers here
],
// A database is optional, but required to persist accounts in a database
// database: process.env.DATABASE_URL,
}
export default (req, res) => NextAuth(req, res, options)
ここではオプションで指定されるProviderとしてGitHubを指定します. Providersページを見るとビルトイン・プロバイダとしてさまざまなサービスに対応していることが分かります.
環境変数の参照
OAuthでの認可にはclient idとclient secretが必要です. これらの認証情報は公開すべきではないでしょう. Next.js 9.4未満ではnext.config.jsに記述していたようですが, 現在は.envのような環境変数ファイルに対応しています.
This document is for Next.js versions 9.4 and up. If you’re using an older version of Next.js, upgrade or refer to Environment Variables in next.config.js.
また開発用, プロダクション用, 秘密情報への対応などの切り替えには以下のようなルールが存在します.
Next.js allows you to set defaults in .env (all environments), .env.development (development environment), and .env.production (production environment).
Note: .env, .env.development, and .env.production files should be included in your repository as they define defaults. .env*.local should be added to .gitignore, as those files are intended to be ignored. .env.local is where secrets can be stored.
以上のことからclient idやclient secretを.env.localに保存して, .gitignoreに加えておけば良さそうです. 上のコマンドでプロジェクトを作成した場合は.env.localはすでに.gitignoreに追加済みです.
.env.local
GITHUB_ID=
GITHUB_SECRET=
クライアント側
クライアント側は普通のReactで作ったWeb UIです. Next.jsではファイル構造がそのままルートとして機能します.
Next.js has a file-system based router built on the concept of pages.
特にpagesディレクトリ直下のindex.jsはルート・ページにマッピングされます.
The router will automatically route files named index to the root of the directory.
pages/index.jsはcreate-next-appでは自動的に作成されていました. この内容を以下のようなログイン・ページに書き換えます.
import React from 'react'
import { signIn, signOut, useSession } from 'next-auth/client'
export default function Page() {
const [session, loading] = useSession()
return <>
{!session && <>
Not signed in <br />
<button onClick={signIn}>Sign in</button>
</>}
{session && <>
Signed in as {session.user.email} <br />
<button onClick={signOut}>Sign out</button>
</>}
</>
}
セッションの共有
ページ間でセッションを共有する必要があります. レイアウトや状態のようなページ間で統一・共有すべき情報はデフォルトの(暗黙的に定義された)Appコンポーネントを上書きして各ページに提供します.
pages直下に_app.jsを作成し以下の内容で保存します.
import { Provider } from 'next-auth/client'
export default function App ({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}
コンテクスト・プロバイダーを利用してsessionの情報が各ページに渡されています.
実行
Next.jsのコマンドライン・インターフェースにはdev, build, startといったサブコマンドが用意されています. またcreate-next-appではこれらを用いたnpmスクリプトも定義されています.
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
今回は開発モードで実行します.
アプリを起動する前にGitHub OAuth AppのAuthorization callback URLがhttp://localhost:3000に設定されているか確認しましょう. それでは実行します.
npm run dev
http://localhost:3000へアクセスすると, Sign inボタンが表示されるのでクリックすると遷移先にSign in with GitHubボタンがあるので更にクリックして認可ページへとリダイレクトします. 認可を済ませると元のページに戻ってきますがホタンの表示がSign outとなっているはずです. これで認可済みであることが分かります.
まとめ
非常に簡単にログイン・ページを作ることができました. 今までの苦労や徒労はなんだったのでしょうか
次回はもう少しNextAuth.jsを掘り下げてscopeの設定やGitHub APIへのアクセスに必要なaccess_tokenのとり方とかを考えたいと思います.
Further Reading
後で別の記事としてまとめようかと思っている項目です.
GitHub OAuth App
ページのカスタマイズ
NextAuth.js automatically creates simple, unbranded authentication pages for handling Sign in, Sign out, Email Verification and displaying error messages.
スコープの扱い
NextAuth関数の正体
NetxAuth関数は非同期関数です.
カスタム・フック関数
Session Context
_app.jsでsession情報を提供するのにContext Providerの機能が使われていました.
useSessionはsessionContextから現在のセッション情報を取り出してログインの有無を判定するのに使われます.
補足
JAMstack
JavaScript, APIs, Markupの頭字語. Next.jsはフロントエンドのフレームワークのように見えますが, 本来はサーバー側の実装であるAPIも作成できます. つまりこれ一つでJAMstackの考え方に基づいたWebアプリケーションが作成できるわけです.
認可と認証
Authと略されると認可(Authorization)と認証(Authentication)は見分けがつかないですね. 英語的には以下のような分類になるようです.
用語 | 意味 |
---|---|
Authentication | the process of proving that something is real, true, or what people say it is |
Authorization | official permission for something to happen, or the act of giving someone official permission to do something |
認証は証とあるように何かを本物とする証明プロセスです. ユーザー認証の場合は, パスワードとか生体情報などを用いてユーザーが本物かどうかを証明するということのようです. 認可は何か(情報へのアクセスなど)をする許可を得るという行為のようです.
GitHubの場合GitHubで認証済みの人(GitHubユーザー)が自分のリソースへアクセスすることを許可することです. 許可の前提としてGitHubにおいて特定のアカウントとして認証されていることが必要です(認証に基づく認可).
ログイン・セッション
この場合のセッションはログイン・セッションのことです. Wikipediaによるとユーザーがログインしている期間を示すようです.
In computing, a login session is the period of activity between a user logging in and logging out of a (multi-user) system.
Login session, https://en.wikipedia.org/w/index.php?title=Login_session&oldid=967667454 (last visited Sept. 15, 2020).
Next.js CLI
おおよそ以下のようにまとめられます.
サブコマンド | 内容 |
---|---|
build | アプリをプロダクション用にビルドする |
dev | アプリを開発用にビルドしローカルホスト上で配信する |
start | アプリをプロダクション用にビルドしローカルホスト上では配信する |
export | 静的ファイルを生成する |