Cognitoで最終ログイン日時を管理してみる。

## はじめに

いきなりですが、認証・認可における`AAAフレームワーク`ってご存知ですか？

`AAAフレームワーク`とは、`Authentication（認証）`、`Authorization（認可）`、`Accounting（アカウンティング）`の３つのAのことです。

https://www.infraexpert.com/study/aaaz01.html

`Accounting`は精算、請求みたいなイメージですが、認証/認可に関する監査/記録といったような意味です。

Cognitoも監査/記録に対応する機能はいくつかあるみたいですが、今回はユーザが最後にログインした日時を記録し、管理画面などで表示する仕組みを考えてみたいと思います。

## Lambdaトリガーをつかった認証フローカスタマイズ

調べてみた感じ、Congnitoのユーザプールには最終ログイン日時を直接取得する機能はないみたいですが、Lambdaトリガーを使うことで実現できそうでした。

Lambdaトリガーは認証方法そのものをカスタマイズしたり、認証フローの特定のタイミングでLambdaの処理をはさむことができる模様です。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-working-with-lambda-triggers.html

今回は認証が成功したタイミングでLambdaの処理をはさむことで、最終ログイン日時を記録するようにしてみます。

## 早速やってみた！

早速実装してみます！

最終ログイン日時をDB等の外部に永続化すると管理画面等で表示する際に取得する処理が必要になってきますので、今回はIDトークンのカスタムクレームに最終ログイン日時を設定するようにしてみます。

### Lambdaの作成

まずは認証後トリガーに設定するLambdaを作成します。

Cognito Admin APIを使い、`custom:last_login`にJSTの日付を設定します。

```javascript:index.js
import pkg from "@aws-sdk/client-cognito-identity-provider";
const { CognitoIdentityProviderClient, AdminUpdateUserAttributesCommand } = pkg;

// Cognitoクライアントの初期化
const cognito = new CognitoIdentityProviderClient({
  region: process.env.AWS_REGION || "ap-northeast-1",
});

/**
 * Cognito Post Authentication Lambda Trigger
 * ユーザーがログインに成功した後に実行され、最終ログイン日時をカスタム属性に保存する
 */
export const handler = async (event, context) => {
  try {
    const userPoolId = event.userPoolId;
    const username = event.userName;

    await updateUserAttributes(userPoolId, username);

    console.log("Post Authentication trigger completed successfully");
    return event;
  } catch (error) {
    console.error("Error in Post Authentication trigger:", error);
    console.log("Continuing authentication flow despite error");
    return event;
  }
};

/**
 * Admin APIを使用してユーザー属性を更新
 */
const updateUserAttributes = async (userPoolId, username) => {
  const now = new Date();
  const jstTime = new Date(now.getTime() + 9 * 60 * 60 * 1000); // UTC+9時間
  const jstString = jstTime.toISOString().replace("Z", "+09:00");

  const command = new AdminUpdateUserAttributesCommand({
    UserPoolId: userPoolId,
    Username: username,
    UserAttributes: [
      {
        Name: "custom:last_login",
        Value: jstString,
      },
    ],
  });

  console.log(
    `Updating user attributes for user: ${username} in pool: ${userPoolId}`
  );
  console.log(`JST Login time: ${jstString}`);
  await cognito.send(command);
  console.log("User attributes updated successfully");
};

```
### 作ったLambdaを設定

作成したLambdaをCognitoユーザプールに登録します。

`拡張機能`からLambdaトリガーを追加します。

![スクリーンショット 2025-05-31 14.46.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/e7ddf648-cf1c-45ce-aa27-69be884b6ba6.png)

`認証`から、`認証後トリガー`を選択し、先ほど作ったLambdaを設定します。

![スクリーンショット 2025-05-31 14.46.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/e77cde27-c62d-4de0-9526-01a469bbd59b.png)

完成です！

![スクリーンショット 2025-05-31 14.47.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/1b97f88b-da56-46ef-8caf-23b723838079.png)

### 試してみる

以前作った適当なWEBサイトでログインし、ユーザの属性の一覧を表示してみると、`custom:last_login`が表示されていることを確認しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/3e1d88f2-a4db-4d02-b589-988d98eea481.png)

IDトークンを見ても、`custom:last_login`が入ってることを確認しました！

```json
{
  "at_hash": "bB4LZfLMc66JWz_QfOj-DA",
  "sub": "47e47ac8-30a1-70cd-64eb-c85956a2be16",
  "email_verified": true,
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXX",
  "cognito:username": "47e47ac8-30a1-70cd-64eb-c85956a2be16",
  "custom:tenant_id": "111",
  "origin_jti": "1b094411-dec2-4725-b945-98f5da093697",
  "aud": "ar4sjg7u1g1t16cah2rjfkih3",
  "event_id": "7e10b06b-42a7-4968-96b0-2e5697ab117e",
  "token_use": "id",
  "auth_time": 1748510122,
  "name": "もり",
  "custom:last_login": "2025-05-29T18:15:20.352+09:00",
  "exp": 1748513722,
  "iat": 1748510122,
  "jti": "66722d73-237b-4356-acc2-c610a08d5593",
  "email": "xxxxxxx@gmail.com",
  "custom:isAdmin": "true"
}
```

なお、このLambdaトリガーは同期的に行われるので、Lambdaがエラーになるとログインそのものも失敗してしまいます。

## 最後に

ちょっと時間取れず、手抜き記事になっちゃいましたが、lamdbaトリガー使えば割と簡単にこういった機能を追加できますので紹介でした！