概要
ブラウザアプリでAmazon Cognitoを利用したログイン機能をつける方法を調べ、実施してみた。
背景と目的
ブラウザアプリにAmazon Cognitoを利用したログイン機能をつけようと思い、かつて実施した
の方法を適用しようと思ったのだが、そのときからだいぶ時間もたっていてAWS SDK for Javascriptのメジャーバージョンがv3になっていた。 かつての方法でももちろん動くのだが、今から古い方法を使って実装するのもどうかと思うので、最新の方法でやってみる。
詳細
0. やりたいこと
以下ができる超簡単なブラウザアプリを作成する。
1. PC開発環境の準備
- Windows11
- Node.js 20.10.0 インストール済み
- 追加の必要ツールインストールも適用済み
@aws-sdk/client-cognito-identity-providerのインストール
npm install @aws-sdk/client-cognito-identity-provider
Cognito
Cognito上に、過去に作成済みのユーザープール、アプリクライアントがあるのでそれらを利用する。後述するユーザー名/パスワードでのログインをするため、アプリクライアントの認証フローで、ALLOW_USER_PASSWORD_AUTHを設定しておく。
APIGateway
使用するAPIは、JSONのパラメータを与えて、JSONのレスポンスが返ってくるものを使う。
使用するAPIのオーソライザーでユーザープールを追加。
使用するメソッドのメソッドリクエストの認可に、ユーザープールを設定。トークンを格納するヘッダ名は、Authorizationとする。
設定が終わったら、デプロイ。
ログインするJavascriptソースコード実装
ユーザー名/パスワードでログインするには、InitiateAuthCommandを用いる。
上記ドキュメントに従って実装。
- パラメータ群inputのAuthFlowは、USER_PASSWORD_AUTHを指定
- ClientIdは、ユーザープールに紐づくアプリクライアントID
- AuthParametersのSECRET_HASHは、アプリクライアントの設定で指定している場合は必要。
// import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider"; const { CognitoIdentityProviderClient, InitiateAuthCommand } = require("@aws-sdk/client-cognito-identity-provider"); const client = new CognitoIdentityProviderClient({ region: "ap-northeast-1" }); async function login_user_password(ClientId, username, password) { const input = { "AuthFlow": "USER_PASSWORD_AUTH", "AuthParameters": { "USERNAME": username, "PASSWORD": password }, "ClientId": ClientId }; const command = new InitiateAuthCommand(input); try { const response = await client.send(command); console.log(JSON.stringify(response, null, "\t")); } catch (error) { console.log(error); } }
成功した場合のレスポンスは以下。
{ "$metadata": { "httpStatusCode": 200, "requestId": "eb3dde55-e498-4cbd-8392-1e727c4f5746", "attempts": 1, "totalRetryDelay": 0 }, "AuthenticationResult": { "AccessToken": "****", "RefreshToken": "****", "IdToken": "****", "TokenType": "Bearer", "ExpiresIn": 3600 }, "ChallengeParameters": {} }
この中で、IdTokenというトークンを後で使用する。
APIGatewayを叩くJavascriptソースコード実装
トークンがもらえたので、それを付加してAPIGatewayを叩くが、この部分はAWS SDKとは関係ないのでどのように実装してもいいのだが、とりあえず以下のような感じ。
APIGateway側の設定で、Authorizationというヘッダにトークンを格納することにしたので、ログイン成功時にもらえたIdTokenをtokenという引数に設定。
// JSONのデータを投げて、JSONのレスポンスをもらう async function postJSON(token, url, data) { try { const response = await fetch(url, { method: "POST", headers: { "Authorization": token, "Content-Type": "application/json" }, body: JSON.stringify(data) }); const result = await response.json(); console.log("success:", JSON.stringify(result, null, "\t")); } catch (error) { console.log("error:", error); } } // 呼び出し const url = "叩くAPIのURL"; data = { // APIにPOSTするJSONパラメータ }; postJSON(response.AuthenticationResult.IdToken, url, data);
成功したら、そのREST APIの正常なレスポンスがもらえた。
{ // JSONレスポンス }
ブラウザ用SDKを作成
AWSのドキュメント
を見ると、 AWS SDK for Javascript V3は、かつてあったようなaws-sdk.min.jsみたいなファイルは用意されていない。そのため、ブラウザアプリに組み込みたければ自分で作れと書いてあった。 方法としては、webpackというツールを用いる。
webpackの準備
CLIもインストール。
npm install --save-dev webpack npm install --save-dev webpack-cli npm install --save-dev path-browserify
作成
src/index.jsに、以下を記述。上記で作成したlogin_user_password、postJSONを記述。success_callback、error_callbackは、本来ここに含めるべきでもないが、試しなのでどうでもいい。最後に、login_buttonは、HTML側で呼べるようにしておく。
import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider"; const REGION = "ap-northeast-1"; const CLIENT_ID = "****"; const client = new CognitoIdentityProviderClient({ region: REGION }); async function login_user_password(username, password, success_callback, error_callback) { const input = { "AuthFlow": "USER_PASSWORD_AUTH", "AuthParameters": { "USERNAME": username, "PASSWORD": password }, "ClientId": CLIENT_ID }; const command = new InitiateAuthCommand(input); try { const response = await client.send(command); success_callback(response); } catch (error) { error_callback(error); } } async function postJSON(token, url, data, success_callback, error_callback) { try { const response = await fetch(url, { method: "POST", headers: { "Authorization": token, "Content-Type": "application/json" }, body: JSON.stringify(data) }); const result = await response.json(); success_callback(result); } catch (error) { error_callback(error); } } function login_success_callback(response) { const url = "****"; const data = { // データ }; postJSON(response.AuthenticationResult.IdToken, url, data, post_success_callback, post_error_callback); } function login_error_callback(error) { console.log("login_error_callback:", error); document.getElementById("result").innerText = "ログイン失敗"; } function post_success_callback(result) { console.log("post_success_callback:", JSON.stringify(result, null, "\t")); document.getElementById("result").innerText = "POST成功"; } function post_error_callback(error) { console.log("post_error_callback:", error); document.getElementById("result").innerText = "POST失敗"; } function login_button() { const username = document.getElementById("userid").value; const password = document.getElementById("password").value; login_user_password(username, password, login_success_callback, login_error_callback); } // Expose the function to the browser window.login_button = login_button;
webpackを使用して、ブラウザ用SDKを作成。
npx webpack --mode production --target web --devtool false
dist/main.jsが作成された。modeをproductionにすると中身が難読化されている。
(()=>{var e={446:(e,t,r)=>{"use strict" :
簡単なブラウザアプリを実装
ユーザー名、パスワードの窓とボタン、結果出力用のdivだけ。先ほど作ったmain.jsを読み込む。
<!DOCTYPE html> <html> <head> <script src="./dist/main.js"></script> </head> <body> <input id="userid" type="text" placeholder="ユーザー名"> <input id="password" type="password" placeholder="パスワード"> <button onclick="login_button();">ログイン</button> <div id="result"></div> </body> </html>
ログインボタンを押したところ、正しく動いた。
Appendix: 仮パスワードの変更
ユーザープールの設定で、新しいユーザーには仮パスワードを与えて変更をさせるようにしてあったので、初めてログインする際に新しいパスワードを設定させる必要があった。なので、新しいパスワードを設定する方法をメモしておく。
- 仮パスワードを変更するには、RespondToAuthChallengeCommandというAPIを使う。
- ChallengeNameは、NEW_PASSWORD_REQUIREDとする。
- Sessionは、InitiateAuthCommandのレスポンスに含まれるSessionを与える。
const { RespondToAuthChallengeCommand } = require("@aws-sdk/client-cognito-identity-provider"); async function change_password(ClientId, session, username, new_password){ const input = { ClientId: ClientId, ChallengeName: "NEW_PASSWORD_REQUIRED", ChallengeResponses: { "USERNAME": username, "NEW_PASSWORD": new_password }, Session: session }; const command = new RespondToAuthChallengeCommand(input); const response = await client.send(command); console.log(JSON.stringify(response, null, "\t")); }
参考
まとめと今後の課題
ブラウザアプリでAmazon Cognitoを利用したログイン機能を実現できた。とりあえず、今後利用できそうだ。