📝

Cognito ユーザープールへの認証処理をフロントエンドとバックエンドで実装してみた

に公開

可能な限り最小構成で実装してみました。

前提

  • 開発環境: Cloud9 (Amazon Linux 2023)

1. Cognito ユーザープールの作成

以下の設定で作成します。

  • アプリケーションタイプ: 従来のウェブアプリケーション
  • サインイン識別子のオプション: メールアドレス
  • リターン URL: http://localhost:3000

アプリケーションクライアントは自動作成されたものを使用せず以下のコマンドで作成します。

$ aws cognito-idp create-user-pool-client \
--user-pool-id your-userpool-id \
--client-name test \
--no-generate-secret \
--explicit-auth-flows "ALLOW_REFRESH_TOKEN_AUTH" "ALLOW_USER_SRP_AUTH" "ALLOW_USER_AUTH" "ALLOW_USER_PASSWORD_AUTH" "ALLOW_ADMIN_USER_PASSWORD_AUTH"

以下の設定でテスト用のユーザーを作成します。

  • E メールアドレス: test@example.com
  • E メールアドレスを検証済みとしてマークする: チェックを入れる
  • パスワードの設定: 任意のパスワードを設定

ユーザー作成後、以下のコマンドでステータスを確認済み変更します。

$ aws cognito-idp admin-set-user-password \
--user-pool-id your-userpool-id \
--username test@example.com \
--password your-passward \
--permanent

2. バックエンド環境の作成

$ mkdir cognito-learning
$ cd cognito-learning
$ mkdir backend
$ cd backend
$ npm init -y
$ npm install express cors @aws-sdk/client-cognito-identity-provider

バックエンドのコードは以下の通りです。
以下の点は環境に合わせて置換してください。

  • your-userpool-id
  • your-client-id
  • your-region
バックエンドのコード
backend/server.js
const express = require('express');
const cors = require('cors');
const { CognitoIdentityProviderClient, AdminInitiateAuthCommand, RespondToAuthChallengeCommand } = require('@aws-sdk/client-cognito-identity-provider');

const app = express();
const PORT = 3001;

const COGNITO_USER_POOL_ID = 'your-userpool-id';
const COGNITO_CLIENT_ID = 'your-client-id';
const COGNITO_REGION = 'your-region';

const cognitoClient = new CognitoIdentityProviderClient({ region: COGNITO_REGION });

app.use(cors());
app.use(express.json());

app.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    if (!email || !password) {
      return res.status(400).json({ error: 'Email and password are required' });
    }

    const command = new AdminInitiateAuthCommand({
      UserPoolId: COGNITO_USER_POOL_ID,
      ClientId: COGNITO_CLIENT_ID,
      AuthFlow: 'ADMIN_NO_SRP_AUTH',
      AuthParameters: {
        USERNAME: email,
        PASSWORD: password,
      },
    });

    const response = await cognitoClient.send(command);

    if (!response.AuthenticationResult) {
      return res.status(400).json({
        success: false,
        error: 'No authentication result returned',
      });
    }

    res.json({
      success: true,
      message: 'Login successful',
      tokens: {
        accessToken: response.AuthenticationResult.AccessToken,
        idToken: response.AuthenticationResult.IdToken,
        refreshToken: response.AuthenticationResult.RefreshToken,
      },
    });
  } catch (error) {
    console.error('Login error:', error);
    res.status(401).json({
      success: false,
      error: error.message,
    });
  }
});

app.listen(PORT, () => {
  console.log(`Backend server running on http://localhost:${PORT}`);
});

package.json に scripts を追記します。

package.json
package.json
{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@aws-sdk/client-cognito-identity-provider": "^3.916.0",
    "cors": "^2.8.5",
    "dotenv": "^17.2.3",
    "express": "^5.1.0"
  }
}

3. バックエンドの動作確認

$ npm start
$ curl -X POST http://localhost:3001/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"!Japwpag5279"}'

以下のような結果を取得できれば成功です。

{
  "success": true,
  "message": "Login successful",
  "tokens": {
    "accessToken": "xxx",
    "idToken": "xxx",
    "refreshToken": "xxx"
  }
}

4. フロントエンド環境の作成

$ cd ~/environment/cognito-learning
$ mkdir frontend
$ cd frontend

フロントエンドのコードは以下の通りです。
以下の点は環境に合わせて置換してください。

  • your-userpool-id
  • your-client-id
フロントエンドのコード
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cognito サインイン - フロントエンド</title>
</head>
<body>
    <h1>Cognito サインイン - フロントエンド</h1>
    
    <div id="loginSection">
        <input type="text" id="email" placeholder="メールアドレス">
        <input type="password" id="password" placeholder="パスワード">
        <button onclick="login()">ログイン</button>
    </div>

    <div id="mainSection" style="display:none;">
        <h2>ログイン成功!</h2>
        <p>Access Token: <span id="accessToken"></span></p>
        <p>ID Token: <span id="idToken"></span></p>
        <p>Refresh Token: <span id="refreshToken"></span></p>
    </div>

    <div id="message"></div>

    <script src="https://unpkg.com/amazon-cognito-identity-js/dist/amazon-cognito-identity.min.js"></script>
    <script>
        const USER_POOL_ID = 'your-userpool-id';
        const CLIENT_ID = 'your-client-id';

        const poolData = {
            UserPoolId: USER_POOL_ID,
            ClientId: CLIENT_ID
        };
        const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

        function login() {
            const email = document.getElementById('email').value;
            const password = document.getElementById('password').value;

            const authenticationData = {
                Username: email,
                Password: password,
            };
            const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);

            const userData = {
                Username: email,
                Pool: userPool
            };
            const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

            cognitoUser.authenticateUser(authenticationDetails, {
                onSuccess: function (result) {
                    document.getElementById('loginSection').style.display = 'none';
                    document.getElementById('mainSection').style.display = 'block';
                    document.getElementById('accessToken').textContent = result.getAccessToken().getJwtToken();
                    document.getElementById('idToken').textContent = result.getIdToken().getJwtToken();
                    document.getElementById('refreshToken').textContent = result.getRefreshToken().getToken();
                },
                onFailure: function(err) {
                    document.getElementById('message').textContent = 'ログインエラー: ' + err.message;
                }
            });
        }
    </script>
</body>
</html>

5. フロントエンドの動作確認

$ python3 -m http.server 3000

プレビューで index.html を表示してユーザー名とパスワードでログインできれば成功です。

まとめ

今回は Cognito ユーザープールへの認証処理をフロントエンドとバックエンドで実装してみました。
どなたかの参考になれば幸いです。

参考資料

Discussion