AWS Cognito トークンの有効期限を設定してNuxtで確認する時の備忘録

# 初めに
前回「Nuxt3 + AWS Cognito スターターセット」という記事を書いた。これはチームメンバーに認証の実装をお願いするために骨組みとして作ったものだが、トークンの認証期限を確認する機能も必要だと思ったので、本記事ではそこを追加実装していく。

https://qiita.com/3dy3day/items/5cee7d1ed6ec9a37ab30

https://github.com/sandy-sunday/nuxt3_cognito_tutorial

# トークンの種類
みんな大好きDevelopersIOにとても丁寧な説明があるので、それを読むことをお勧めする。
簡単にまとめると、以下の３種のトークンがある。

**idToken**: いわゆる認証に使用
**accessToken**: ユーザー属性の追加・変更・削除に使用
**refreshToken**: 新しいIDトークンおよびアクセストークンの取得に使用

https://dev.classmethod.jp/articles/study-tokens-of-cognito-user-pools/

# トークンの有効期限を設定（cognito側）
idToken, accessToken, refreshTokenはそれぞれデフォルトで60分、60分、30日の有効期限が設定されている。ここでは、各時間を短く設定し、web側でトークンの有効期限が切れていないか確認し、切れていた場合ログインページにリダイレクトするように実装していく。

Amazon Cognito > ユーザープール > 作成したユーザープール > アプリケーションの統合 > アプリクライアントと分析
にて、作成したアプリケーションクライアントを選択。アプリケーションクライアントに関する情報の右側にある「編集」を押すと各有効時間を設定できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/1f21f10b-8bd3-2464-0c5c-3f1d214cd8cb.png)

ひとまず更新トークンの有効期限を最短の60分に設定する。

# トークンの有効期限を確認（Nuxt側）
```cognitoUser.getSession()```で基本的なセッション情報を確認できる。

```shell
{
    "sub": "something-something-something-something-something",
    "email_verified": true,
    "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_something",
    "cognito:username": "something-something-something-something-something",
    "origin_jti": "something-something-something-something-something",
    "aud": "something",
    "event_id": "something-something-something-something-something",
    "token_use": "id",
    "auth_time": 1698910584,
    "exp": 1698914184,
    "iat": 1698910584,
    "jti": "something-something-something-something-something",
    "email": "fugafuga@gmail.com"
}
```

各ページが読み込まれる前に有効なセッションがあるかどうかを確認するため、ミドルウェアとして```auth.ts```を作成する。

```ts
// middleware/auth.ts
import { CognitoUserPool } from 'amazon-cognito-identity-js';

export default defineNuxtRouteMiddleware((to, from) => {
    const nuxtApp = useNuxtApp();
    const userPool = nuxtApp.$userPool as CognitoUserPool;
    const cognitoUser = userPool.getCurrentUser();

    if (cognitoUser) {
        cognitoUser.getSession(function (err: any, session: { isValid: () => any; }) {
            if (err) {
                console.error('Error retrieving session:', err);
                return navigateTo('/login');
            }

            if (session.isValid()) {
                console.log('User has a valid session');
                return;
            } else {
                console.log('User\'s session has expired');
                return navigateTo('/login');
            }
        });
    }
    else {
        console.log('User doesn\'t have a valid session');
        return navigateTo('/login');
    }
})
```

有効なセッションが無い時にログインページにリダイレクトさせるのは、ログイン後に到達するページの為、ここでは```login-success.vue```のみが該当することになる。```<script setup>```内に入れておけば勝手に検証をしてくれる。

```ts
definePageMeta({
  middleware: ['auth']
})
```
# 詰まりポイント
## global middleware で無限ループに入る
どうせ全ページで有効なログインセッションがあるか確認すると思い、ファイル名を```auth.global.ts```にしてグローバルなミドルウェアを作成したが、有効なセッションが無いため、一生ログインにリダイレクトされ続ける無限ループに入った。

https://blog.cloud-acct.com/posts/nuxt3-global-middleware/#navigateto%E3%81%AF%E7%84%A1%E9%99%90%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AB%E3%81%AA%E3%82%8B%E3%83%90%E3%82%B0%E3%81%82%E3%82%8A

対策としてセッションを検証するミドルウェアをローカルにしてログイン後の各ページで呼び出し、ログインページは別でコードを書いた。
```ts
// pages/login.vue

if (cognitoUser) {
  cognitoUser.getSession(function (err, session) {
    if (err) {
      console.error("Error retrieving session:", err);
      return;
    }

    if (session && session.isValid()) { // Check if user already has a valid session
      console.log("User is logged in.");
      return navigateTo('/login-success');
    } else {
      console.log("Session is not valid or expired.");
      return; //do nothing when there is no valid session
    }
  });
} else {
  console.log("User is not logged in.");
}
```
## nuxtAppでprovideしたのに型が読み込めない
各ページでcognito のライブラリを使用するため、初期化はプラグインで行い、他ページではそれを呼び出す作りにした。

```ts
// plugins/cognito.ts

import { CognitoUserPool } from 'amazon-cognito-identity-js';
import { defineNuxtPlugin } from '#app';

export default defineNuxtPlugin((nuxtApp) => {
  const runtimeConfig = useRuntimeConfig();

  const poolData = {
    UserPoolId: runtimeConfig.public.userPoolId as string,
    ClientId: runtimeConfig.public.clientId as string,
  };

  const userPool = new CognitoUserPool(poolData);
  nuxtApp.provide('userPool', userPool);
});

```

しかし、```auth.ts```等でuserPoolを呼び出そうとしても、型情報が抜け落ちてしまい、組み込まれているメソッドを呼び出すことができなかった。
```shell
'userPool' is of type 'unknown'.
```

暫定的な処置として、呼び出し先で型を再度明示することでエラーを回避できた。
```ts
const nuxtApp = useNuxtApp();
const userPool = nuxtApp.$userPool as CognitoUserPool;
const cognitoUser = userPool.getCurrentUser();
```