Next.jsで特定ページに認証（ログインチェック）を書けるシンプルな方法


next.jsで特定のページに認証を書ける方法のメモ。
認証を行うAuthコンポーネントを作成し、それで囲めば認証がかかる方法のチェック。

>NextAuthとかのライブラリもありますが、一旦、「素の実装方法」を確認しておきたい。

## やりたいこと

下記のようなフローを作りたい。

![スクリーンショット 2021-09-05 10.48.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/a70baecc-6226-f5fb-8ffd-302f8b4f10d5.png "スクリーンショット 2021-09-05 10.48.14.png")


## 準備

検証用のプロジェクト差k末井。

```bash
npx create-next-app next-auth-test
cd next-auth-test
```

今回は認証の方式（ログインの維持）としてCookieに認証情報を書き込んでそれが有るか、無いかでログイン状態を判断する実装としたいと思いますので、Cookieを簡単に扱うライブラリを入れます。

>react-cookieってライブラリも試したのですが、未使用の変数や関数を定義する必要がありwarningがウザイのでjs-cookieを利用します。

```bash
npm install js-cookie
```

認証を行うAuthコンポーネントを記述するauth.jsをcomponents以下に作成。

```bash
mkdir components
touch components/auth.js
```

認証画面を担うlogin.jsと認証対象となるprivate.jsを作成。

```bash
touch pages/login.js pages/private.js
```

## 実装

では実装していきます。

### auth.js

Cookieの有無をチェックしてあればそのまま、なければloginにリダイレクトさせるだけ。
Cookieチェックロジックをいろいろな方式に切り替えればいろいろ応用はできる想定。

>Cookieに書き込む情報を認証Cookieとわからない名前の方がいいですし、安易に生成できない値がいいですし、できればサーバサイドでhttpOnlyで処理したほうがいいです(クライアントサイドでsignedIn=true等は直ぐに書き込めるので良くないです）。

childrenをpropsで受けとりreturnすれば子コンポーネントのレンダリングに進める。

```js:components/auth.js
import { useRouter } from "next/router";
import Cookies from "js-cookie";

const Auth = ({ children }) => {

    //router
    const router = useRouter();

    //Cookieのチェック（これをいろいろ認証タイプにより変更）
    const signedIn = Cookies.get("signedIn");
    //signedInがtrueじゃなければ/loginへ
    if (signedIn !== "true") router.replace("/login");

    //何もなければ次へ（そのまま処理）
    return children;
}

export default Auth;
```

### login.js

簡易ログインん画面を作成。
ここではログインボタン押すと無条件にログインしちゃいますが、ここでID, Password認証などを行えばいいでしょう。

```js:login.js
import Link from "next/link";
import { useRouter } from "next/router";
import Cookies from "js-cookie";

const Login = () => {

    const router = useRouter();

    //ログイン処理（CookieにsignedIn=trueとする）
    const login = () => {
        Cookies.set("signedIn", "true");
        router.replace("/private");
    }

    return (
        <>
            <h1>Login</h1>
            <button onClick={login}>ログイン</button>
            <div>
                <Link href="/"><a>Homeへ</a></Link>
            </div>
        </>
    );
}

export default Login;
```

### index.js

Homeページ。/privateにリンクを貼っているだけ。

```js:index.js
import Link from "next/link";

const Home = () => {
  return (
    <>
      <h1>Home</h1>
      <div>
        <Link href="/private"><a>Privateへ</a></Link>
      </div>
    </>
  );
}

export default Home;
```

### private.js

Authタグで囲んでアクセス時に認証状態であるかをチェックしています。
OKならそのまま表示、NGなら/loginへリダイレクト。
ログアウトはただCookieを消しているだけ。

```js:private.js
import Link from "next/link";
import { useRouter } from "next/router";
import Cookies from "js-cookie";

//認証コンポーネント読み込み
import Auth from "../components/auth";

const Private = () => {

    const router = useRouter();

    //ログアウト処理
    const logout = () => {
        Cookies.remove("signedIn");
        router.replace("/login");
    }

    return (
        <Auth>
            <h1>Private</h1>
            <button onClick={logout}>ログアウト</button>
            <div>
                <Link href="/"><a>Homeへ</a></Link>
            </div>
        </Auth>
    );
}

export default Private;
```

react-router-domによるルーティングの方が認証においてはわかりやすいかな。。。

## おまけ

### cookieの偽装

ここではsignedIn=trueであればログイン状態としました。が、この状態はユーザーやXXS脆弱性があれば直ぐに偽造できます。一番簡単な方法はブラウザの開発者ツールのコンソールでJSを実行することです。

下記のようにコンソールにて、

```bash
document.cookie="signedIn=true"
```

とすることでログイン状態にすることができます。

![スクリーンショット 2021-09-05 12.46.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/03fab72a-b361-8467-11f2-d4a5beb31f83.png "スクリーンショット 2021-09-05 12.46.28.png")

安全策としては、

* とにかく認証情報をわからないような情報とする
* 安易に生成できない、かつユーザー依存情報とする（不正が発生しても最悪、個人単位）
  * SPAならtoken情報を取得するのでとらいあえずそれを利用するなど
* サーバ側管理とする
* httpOnlyでサーバ側でCookie生成、削除を行う

とかでしょうかね。