Next.js メール＆パスワードを用いたログイン認可

## きっかけ
モダンなフレームワークを使うと、ページを跨いで状態を管理するのが課題になることが多いですよね。特に、Next.jsを使ってログイン機能を実装する際、ユーザーがログインしている場合にのみ特定のページにアクセスできるようにしたいと考えました。この記事では、そんなログイン認可を実現するための方法を詳しく解説します。

## ログイン情報を保持する場所

まず、ログインしたという情報をどこで管理するかを考えましょう。以下のような選択肢があります。

| 保持場所        | メリット                                                                                                                                   | デメリット                                                                                                                                          |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Cookie**      | - サーバーサイドでの認証に便利<br>- セキュアな通信（HTTPS）を利用することで比較的安全<br>- クライアント間での共有が容易                                           | - サイズ制限がある（通常4KB）<br>- XSS（クロスサイトスクリプティング）攻撃に弱い<br>- CSRF（クロスサイトリクエストフォージェリ）攻撃のリスク |
| **LocalStorage**| - クライアントサイドでのデータ保持に便利<br>- 容量が大きい（約5MB）<br>- シンプルなAPIで使いやすい                                             | - 永続的にデータが保持されるため、ログアウト処理を明確にする必要がある<br>- セキュリティ上のリスク（XSS攻撃に弱い）                                        |
| **SessionStorage** | - ブラウザのセッションが終了するとデータが消えるため、一時的なデータ保持に適している<br>- セッション単位での管理が容易                                        | - ブラウザを閉じるとデータが失われる<br>- 容量が小さい（約5MB）<br>- XSS攻撃に弱い                                                                     |
| **データベース** | - 永続的なデータ管理が可能<br>- サーバーサイドでの処理によりセキュリティが高い<br>- 大量のデータ管理に適している                                            | - サーバーサイドでの処理が必要<br>- データベースの管理・保守が必要<br>- レスポンスの速度が遅くなる可能性がある                                            |

:::note info
例えば、 **LocalStorage**を用いた場合、保存された情報を取り出す際に遅延が生じることがあるため、ページのレンダリングのタイミングを考えなければならないことがあります。
**Cookie**での管理はとっつきにくい部分が大きいですが、`API Routes`と`Routes Handler`と呼ばれる二種類のfetch方法を理解すれば実現できると思います。
他にも**firebase**や**supabase**などのBaasを用いる場合はそれらを用いた状態管理を行う方が楽になることが多いです。
個人の小規模開発においては、**LocalStorage**を使ってしまうことも多いでしょう。
:::

## ページ遷移の制限をどこで実装するか

次に保存したログイン情報から、**ログイン認可**（ページ遷移を制限する）方法を考えましょう。

### `Middleware.ts`を使用する

Middleware.tsとはNext.jsのServerSideとClientSideの間に位置するファイルでCookieの情報をこのファイルで取得し検証することによりページ遷移先を制限することが可能になります！
ただしLocalStorageなどクライアントで状態管理している場合値を取ってこれないため注意が必要です。

#### サンプルコード

```javascript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(req: NextRequest) {
  const token = req.cookies.get('token');

  if (!token) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*'],
};
```

### `各ページでログイン状態を取得する`
一番直感的な考え方です。
各ページごとに保存先からログイン状態を取得し、ログインでない場合ページ遷移を実装する方法です。

:::note alert
状態を取得している間にレンダリングされページの内容が表示されることを防ぐために、専用のコンポーネントで囲うなど対策をすることが必要です。
:::

#### サンプルコード
```js
// pages/dashboard.js
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

const Dashboard = () => {
  const router = useRouter();

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (!token) {
      router.push('/login');
    }
  }, []);

  return <div>Welcome to the dashboard!</div>;
};

export default Dashboard;
```

## まとめ

ログイン情報の保持場所やページ遷移の制限方法には、それぞれメリット・デメリットがあります。最適な方法は、プロジェクトの要件やセキュリティ上の懸念事項によって異なります。

Cookie、LocalStorage、SessionStorage、データベースなど、さまざまな選択肢がありますが、セキュリティとパフォーマンスのバランスを考慮する必要があります。個人の小規模開発では LocalStorage を使うことも多いでしょう。一方、本格的なウェブアプリケーションでは、Cookie や Firebase、Supabase などの BaaS を活用するのが賢明でしょう。

ページ遷移の制限に関しても、Middleware.ts を使う方法と、各ページでログイン状態を取得する方法があります。前者はサーバーサイドで制御できる一方、後者はクライアントサイドで実装するため、レンダリングのタイミングに注意が必要です。

### 追記
Cookie,LocalStorage,Middlewareを用いた例を置いておきます！

https://github.com/mon50/nextjs14-simple-auth/tree/main