【AWS Amplify】Next.js Middleware で Cognito グループの認可処理を行う

# はじめに

こんにちは、梅雨です。

今回は、Next.js Middleware で Cognito ユーザの所属するグループによって認可処理を行う方法について解説していこうと思います。

# AWS Amplify とは？

AWS Amplify は、フロントエンドとクラウドバックエンドを効率に構築・デプロイするためのフレームワークです。

https://aws.amazon.com/jp/amplify

Amplify を使うことで、認証、DB、API などのバックエンド機能を簡単にセットアップでき、フロントエンドとシームレスに統合することができます。

認証機能に関しては、Amazon Cognito と統合することでユーザのサインインやサインアップを短時間で実装できます。

# 実装

早速実装に移っていきましょう。

## ランナーの準備

まずは `createServerRunner` 関数でサーバランナーを用意します。

この関数は `@aws-amplify/adapter-nextjs` パッケージから提供されています。

```bash
npm i @aws-amplify/adapter-nextjs
```

```ts
import config from "src/aws-exports";
import { createServerRunner } from "@aws-amplify/adapter-nextjs";

const { runWithAmplifyServerContext } = createServerRunner({
  config,
});
```

## 通常の認証

続いて、Cognito ユーザの通常の認証を行なっていきます。

さきほど作成したランナー内で `fetchAuthSession` 関数を用いてユーザセッションを取得し、セッションが存在しないまたはセッショントークンが存在しないときは強制的に `/login` に飛ばすようにしています。

```ts:src/middleware.ts
const middleware = async (request: NextRequest) => {
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      try {
        const authSession = await fetchAuthSession(contextSpec, {});
        return authSession.tokens !== undefined;
      } catch (error) {
        console.log(error);
        return false;
      }
    },
  });

  if (authenticated) {
    if (request.nextUrl.pathname !== "/login") return;
    request.nextUrl.pathname = "/";
    return NextResponse.redirect(request.nextUrl);
  } else {
    if (request.nextUrl.pathname === "/login") return;
    request.nextUrl.pathname = "/login";
    return NextResponse.redirect(request.nextUrl);
  }
};

export default middleware;
```

## Cognito グループによる認可処理

最後に、本題となるCognito グループによる認可処理を記述していきます。

先ほど取得したユーザセッションには Amplify によって検証が行われた JWT 形式のアクセストークンが含まれています。

JWT のペイロードは `cognito:groups` という名前で認証ユーザの所属するグループを所持しています。このグループはグループ名の配列形式をとります。

```json
["admin", "maintainer"]
```

```diff_typescript
const authSession = await fetchAuthSession(contextSpec, {});
+ const groups = authSession.tokens?.accessToken.payload[
+     "cognito:groups"
+ ] as string[] | undefined;
```

続いて、この配列を用いて認可を行います。

今回の例では、`admin` グループと `maintainer` グループに所属するユーザのみがアクセスできるようにします。

```diff_typescript
const authSession = await fetchAuthSession(contextSpec, {});
const groups = authSession.tokens?.accessToken.payload[
    "cognito:groups"
] as string[] | undefined;
+ return !!(groups?.includes("admin") || groups?.includes("maintainer"));
```

これによって、`groups?.includes("admin")` と `groups?.includes("maintainer"))` が両方とも `false` （または `undefined`）のとき、全体として `false` を返すようになりました。

## 最終的なコード

最終的なコードは以下のようになっています。

```ts:src/middleware.ts
const middleware = async (request: NextRequest) => {
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      try {
        const authSession = await fetchAuthSession(contextSpec, {});
        const groups = authSession.tokens?.accessToken.payload[
          "cognito:groups"
        ] as string[] | undefined;
        return !!(groups?.includes("admin") || groups?.includes("maintainer"));
      } catch (error) {
        console.log(error);
        return false;
      }
    },
  });

  if (authenticated) {
    if (request.nextUrl.pathname !== "/login") return;
    request.nextUrl.pathname = "/";
    return NextResponse.redirect(request.nextUrl);
  } else {
    if (request.nextUrl.pathname === "/login") return;
    request.nextUrl.pathname = "/login";
    return NextResponse.redirect(request.nextUrl);
  }
};

export default middleware;
```

# おわりに

今回の記事では、Next.js の Middleware で Cognito グループによる認可処理を行う方法について解説しました。

Amplify では現状複数の Cognito プールと接続することはできないため、異なる属性を持つユーザを扱うためにはグループによる制御が欠かせません。

Next.js でグループによる認可制御を行う際は是非参考にしてみてください。

最後までお読みいただきありがとうございました。