ログインページにリダイレクトしたい

# この記事について
Next.jsで、ログイン必須のページにアクセスされたとき、ログインしていなければ強制的にログインページにリダイレクトする仕組みを紹介する。

# 準備
- Nextプロジェクトのルートディレクトリに`plugin`ファルダを作成し、その中に`axios.ts`ファイルを作成する
- `npm install axios`または`yarn add axios`等でaxiosをインストールする

# 実装

```typescript:plugin/axios.ts
import axios from "axios";

const axios_instance = axios.create({
    headers:{
        "Content-Type": "application/json",
    },
});

axios_instance.interceptors.request.use(

    function(config){

        return config;
    },

    function (error){

        return Promise.reject(error);
    }
);

axios_instance.interceptors.response.use(

    function (response){

        return response;
    },

    function (error){

        const originalConfig = error.config;
        if(
            error.response &&
            error.response.status === 401 &&
            !originalConfig.retry
        ){
            //認証エラーならリトライ
            originalConfig.retry = true;

            //以下の場合はリトライしない
            if(originalConfig.url === "ログインAPIへのパス"){

                return Promise.reject(error);
            }
            
            axios_instance
            .post("リトライAPIへのパス", {refresh:""})
            .then((response) => {
                return axios_instance(originalConfig);
            })
            .catch(function(error){
                return Promise.reject(error);
            });
        }
        else if(error.response && error.response.status !== 422){

            window.location.href = "ログインAPIへのパス";
        }
        else{
            
            return Promise.reject(error);
        }
    }
);

export default axios_instance;
```

# 解説
## axiosインスタンスの作成
```typescript:共通ヘッダーの設定
const axios_instance = axios.create({
    headers:{
        "Content-Type": "application/json",
    },
});
```
Content-Type が "application/json" に設定された axios インスタンスを作成している。
これにより、送信されるリクエストはすべて JSON 形式でデータを扱うことが前提となる。

## インターセプタ
```typescript:リクエストインターセプタ
axios_instance.interceptors.request.use(

    function(config){

        return config;
    },

    function (error){

        return Promise.reject(error);
    }
);
```
リクエストが送信される前にインターセプターが実行され、渡された`config`（リクエストの設定情報）をそのまま返す。また、エラーが発生した場合は、単に`Promise.reject(error)`でエラーを返す。

```typescript: レスポンスインターセプタ
axios_instance.interceptors.response.use(

    function (response){

        return response;
    },

    function (error){

        const originalConfig = error.config;
        if(
            error.response &&
            error.response.status === 401 &&
            !originalConfig.retry
        ){
            //認証エラーならリトライ
            originalConfig.retry = true;

            //以下の場合はリトライしない
            if(originalConfig.url === "ログインAPIへのパス"){

                return Promise.reject(error);
            }
            
            axios_instance
            .post("リトライAPIへのパス", {refresh:""})
            .then((response) => {
                return axios_instance(originalConfig);
            })
            .catch(function(error){
                return Promise.reject(error);
            });
        }
        else if(error.response && error.response.status !== 422){

            window.location.href = "ログインAPIへのパス";
        }
        else{
            
            return Promise.reject(error);
        }
    }
);
```
レスポンスが正常に返ってきた場合は、レスポンスそのものをそのまま返すが、エラー時には以下のように処理される
- 認証エラー（401）の場合:

    エラーが401（Unauthorized）で、かつリトライがまだ行われていない場合（originalConfig.retry が false の場合）に処理が進む。
    ログインAPIのエンドポイントに対してPOSTリクエストを送信して認証情報のリフレッシュ、成功した場合は元のリクエスト（originalConfig）を再実行する。
    ただし、リクエスト先が`ログインAPIへのパス`の場合は、ログイン自体のエラーと判断しリトライは行わない。

:::note info
originalConfig.retry を true に設定し、同じリクエストの二重リトライを防止。
:::

- 422エラーの場合:

    422エラーは特定のバリデーションエラーや処理上のエラーと判断され、そのままエラーを返す。
    　
- その他のエラー（422以外）の場合:

    エラーステータスが422以外であれば、ユーザーをログインページにリダイレクトする。これは、認証が失敗している場合など、再ログインが必要と判断された場合の処理。

# 使い方

axiosをインポートする際に、`import axios from "@/plugin/axios";`としてインポートする。

以降は普通のaxiosと同じようにリクエストを扱える

:::note warn
この場合、バックエンドへのリクエストは`axios`で行い、Next.js内への通信は`fetch`で行うなどの戦略が考えられる
:::