[Next.js] API Routes と iron-session を使ってカスタムの認証を実装する
こんにちは、 kenzauros です。
Next.js でユーザー認証といえば、 NextAuth.js がメジャーどころで、私もいくつか記事を書いています。
外部の認証プロバイダーを使う場合などは特に便利ですし、 CredentialsProvider を実装すれば独自の認証を組み込むこともできます。
ただ「独自の認証」だけでいい場合は少し充実しすぎな感もあります。そこで今回は公式の “Authentication” で紹介されている iron-session を用いた方法 を試してみました。
ちなみに公式でも下記のような使い分けがおすすめされています。
If you want a low-level, encrypted, and stateless session utility use iron-session.
低レベルで暗号化されていてステートレスなセッションなら iron-session
If you want a full-featured authentication system with built-in providers (Google, Facebook, GitHub…), JWT, JWE, email/password, magic links and more… use next-auth.
マッチョな機能が全部入りな認証システムをお求めなら next-auth
iron-session について
iron-session はセッションベースのセッション管理ユーティリティーです。
下記のような特徴があります。
- Cookie をセキュアに保つことができる
- HttpOnly, HTTPS のみにできる
- 暗号化され、サーバー側でのみデコード可能
- ステートレス
- セッション ID なし
- サーバー側でセッション情報管理の必要なし
ステートレスな認可という意味では JWT を用いる方法も一般的ですが、 SPA でない Next.js の場合はセッションを使った認可のほうがわかりやすいかもしれません。
デメリットとしてはクライアント側の Cookie にセッション情報が記録されているため、サーバー側からセッションを無効化する方法がないことでしょうか。
環境
- Ubuntu 20.02 (Windows 11 Pro WSL2)
- Node.js 18.12.1
- React.js 18.0.27
- Next.js 13.1.2
※ 現時点で Next.js の app Directory は beta のため使用していません。
概要
今回のデモアプリは下記のリポジトリにありますので、参照してください。
ベースは冒頭に紹介した公式サンプルです。
いくらか使いにくい部分があったり、不足している部分があったので、追加・修正しています。
なお、今回はボイラープレートとして使えるよう、認証ロジックをダミーにしています。認証のカスタムロジックは lib/database.ts
の tryLogin
を用途に応じて実装することでカスタムできますが、もちろんまったく別の方法で実装しても問題ありません。
ファイル構成
リポジトリのファイル構成は下記のように最小限の構成になっています。
.
├── lib
│ ├── database.ts
│ ├── fetchJson.ts
│ ├── session.ts
│ └── useAuth.ts
├── pages
│ ├── api
│ │ ├── login.ts
│ │ ├── logout.ts
│ │ └── user.ts
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── index.tsx
│ └── login.tsx
├── next.config.js
├── package-lock.json
├── package.json
└── tsconfig.json
簡単に各ファイルを説明します。
lib
database.ts
認証のためのダミー関数tryLogin
を含んでいます。本来は認証のカスタムロジックを実装しますが、関数はダミーでユーザー名hoge
、パスワードhogehoge
の場合のみログインできるようになっています。fetchJson.ts
fetch
のラッパーです。サンプルのものをベースに、ヘッダー指定などを内部で行うよう改良しています。session.ts
iron-session の設定とセッションの型定義、 Next.js のgetServerSideProps
を生成するための関数を含んでいます。useAuth.ts
ページで使用する React Hooks です。ログイン中のユーザー情報などのほか、フロントエンドから呼び出すログイン・ログアウト関数を提供します。
pages
api
: API はいずれもlib/useAuth.ts
から呼び出します。login.ts
: ログイン API です。logout.ts
: ログアウト API です。user.ts
: セッション情報からユーザー情報を返す API です。
_app.tsx
ユーザー情報の管理に SWR を利用しているため、基本設定をここのSWRConfig
で行っています。index.tsx
ログイン後に表示されるトップページです。login.tsx
ログイン画面です。
実行
npm run dev
で実行すると http://localhost:3000/login でログイン画面が表示されます。
ユーザー名 hoge
、パスワード hogehoge
でログインできます。
ユーザー名かパスワードを間違うとログインに失敗し、 Authentication failed が表示されます。
ログインすると /
にリダイレクトされます。ログイン中のユーザー名とログアウトボタンが表示されます。
ログアウトボタンをクリックするとログアウトしてログイン画面にリダイレクトされます。
大まかな処理の流れ
ログイン処理
ログインは下記のような流れになります。
ページ表示
ページ表示時のサーバーサイド (SSR) の処理は下記のようになります。
SG (Static Generation) でフロントエンド側からユーザー情報が必要な場合は API Route の /api/user
から SWR で取得したユーザー情報を利用します。 useAuth
の user
を参照します。
ログアウト処理
ログアウトはデータベースリクエストがないだけで、ログインとほぼ同様の流れです。 API 内でセッションを破棄すれば完了です。
その他のページの実装
API
API Route の場合、通常通り pages/api
以下にハンドラー設定した ApiRoute を配置します。
iron-session がもつ withIronSessionApiRoute
でラップすれば、 req.session
でユーザー情報を含むセッション情報を参照できます。
/api/user を参考にするとよいでしょう。
ページ
こちらも Next.js のお作法通り、 pages
以下に tsx
ファイルを配置すれば OK です。
こちらは lib/session
で定義している withUserSessionSsr
を使うことで未認証時のリダイレクトを書かなくてよくなります。
export const getServerSideProps = withUserSessionSsr("/login");
SSR でサーバーサイドの処理が必要な場合は第2引数にハンドラー関数を設定します。セッション情報は req.session
から参照できます。
export const getServerSideProps = withUserSessionSsr("/login", (({ req, res }) => {
const user = req.session.user;
// なんらかの処理
return {
props: {
user,
},
};
}));
まとめ
今回は Next.js で NextAuth.js を使わず、 API Routes と iron-session を使ってカスタムの認証を実装する方法を紹介しました。
認証部分は用途に応じて実装が必要ですが、ボイラープレートとして使えると思います。
どなたかのお役に立てば幸いです。