🔷 はじめに
API Gatewayに作成したAPIを、認証済みのユーザーのみに利用を許可したい場合、API Gatewayのオーサライザーという機能を使う事で実現できます。
フロントエンドからAPIにリクエストを送る際に、認証情報を付加してリクエストを送ると、オーサライザーで認証情報が正しいものか判断して、正しければAPIの利用が許可されて、APIからフロントエンドにレスポンスが返るという流れになります。
🔶 オーサライザー
API Gatewayにある、認可を行う機能です。
認可は、リクエスト時に付加されている認証情報を解析して、正しいものであればAPIの利用を許可する、という形で行います。
それだけの機能なので、Lambdaで自作したり、Auth0ののような外部サービスを利用したり、実現手段はいくつかありますが、今回はCognitoを使用して認可機能を実現します。
🔶 Cognito
Amazon Cognito は、ウェブアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートしています。ユーザーは、ユーザー名とパスワードを使用して直接サインインするか、Facebook、Amazon、Google などのサードパーティーを通じてサインインできます。
今回は、Cognitoが持つ認可機能を利用して、オーサライザーを作成します。
🔶 認証情報
フロントエンドがリクエスト時に付加する認証情報を、どのように取得するかと言うと、Cognitoに発行してもらいます。
認証情報というのは、簡単に言うとIDトークンと呼ばれる、ユーザー情報等をエンコードした文字列です。
Cognitoにユーザーの認証情報を発行してもらう方法として、今回はユーザープール認証フローで行います。
流れとしては上図になりますが、もう少し細かい流れを言うと、事前にCognitoのユーザープール(後述)にユーザーを登録した上で、以下のようになります。
- フロントエンドがCognitoのInitiateAuth APIに、ユーザーのIDとPWを渡す。
- CognitoがIDとPWでユーザー認証を行い、問題が無ければChallengeというパラメータをフロントエンドに返す。
- フロントエンドがCognitoのRespondToAuthChallenge APIに、Challengeというパラメータを渡す。
- CognitoがChallengeの確認を行い、認証した証明として認証情報(IDトークン)をフロントエンドに返す。
ただし、実際に実装する際はSDK等が用意されているので、コードとしてシンプルなものになります。
一見すると、OpenID Connectのフローのようですが、Cognitoは全く別物の、AWS独自の認証フローになります。
🔷 作業手順
作業手順は以下になります。
- API GatewayでAPIの作成
- Cognitoでユーザープールの作成
- ユーザープールに、ユーザーを作成
- IDトークンを取得する、フロントエンドの作成
- API Gatewayでオーサライザーの作成
🔷 API GatewayでAPIの作成
今回はテストなので、標準で用意されている、PetStoreというサンプルAPIを使用します。
上図の「APIの例」を選択すると、PetStoreというAPIが作られます。
🔷 Cognitoでユーザープールの作成
今回はユーザープール認証フローで、Cognitoにユーザーの認証情報のリクエストを行います。
そのためには、Cognitoにユーザーを登録する必要があります。
そして、Cognitoにユーザーを登録するには、ユーザープールというディレクトリを用意する必要があります。
1. ユーザープールを新規作成する。
まずはCognitoの画面を開き、[ユーザープールの管理]ボタンをクリックします。
[ユーザープールを作成する]ボタンをクリックします。
プール名を入力して、[ステップに従って設定する]ボタンをクリックします。
⬛プール名
任意の名称をセットします。
2. 作成するユーザープールの、属性の設定。
今回は、ユーザーがサインインする際は、メールアドレスでサインインするようにします。
[検証済みの E メールアドレスでのサインインも許可]にチェックを入れます。
下までスクロールすると、標準属性のemailにチェックが入っているので、今回はテストなので条件を緩くするため、チェックを外します。
3. 作成するユーザープールの、ポリシーの設定。
パスワード強度のチェックがすべて入っていますが、今回はテストなので、チェックを全て外して条件を緩くします。
4. 作成するユーザープールの、アプリクライアントの設定。
左メニューの[アプリクライアント]をクリックします。
[アプリクライアントの追加]をクリックします。
以下の必要項目をセットします。
⬛アプリクライアント名
任意の名称をセットします。
⬛[クライアントシークレットを生成]チェックボックス
今回は手順簡略化のため外します。
⬛[サーバーベースの認証でサインイン API を有効にする (ADMIN_NO_SRP_AUTH)] チェックボックス
チェックを入れて、サーバー側認証を有効にします。
5. ユーザープールの作成実行。
最後に確認画面が表示されるので、[プールの作成]ボタンをクリックします。
🔷 ユーザープールに、ユーザーを作成
左メニューの[ユーザーとグループ]をクリックします。
[ユーザーの作成]ボタンをクリックします。
ユーザーの作成ダイアログが表示されるので、以下の必要項目をセットします。
⬛ユーザー名
任意の名称をセットします。
⬛[Eメール]チェックボックス
ユーザー登録に成功すると、IDとPWが記載された招待メールが届くので、チェックを入れます。
⬛仮パスワード
任意の仮パスワードをセットします。
今回はユーザープール作成時にポリシーを緩くしたので、8文字以上であれば問題ないです。
ただし、仮パスワードは、ユーザー側で変更が必須となります。
⬛[電話番号を検証済みにしますか?]チェックボックス
今回は電話番号を登録しないので、チェックを外します。
⬛Eメール
任意のメールアドレスをセットします。
適当に入力しても登録はできるけど、招待メールの送信先になるので、テ
ストにしても使い捨てメールでも良いので有効なメールアドレスをセットしましょう。
アカウントのステータスを確認すると、FORCE_CHANGE_PASSWORDというパスワード変更が必要な状態になっています。
管理者が作成したユーザーは、パスワードの変更が必須ですが、後述のフロントエンド処理の中で、パスワード変更も行いたいと思います。
参考:例: SDK for JavaScript で AdminCreateUser API を使用して作成されたユーザーの新しいパスワードの認証と設定
🔷 IDトークンを取得する、フロントエンドの作成
今回一番面倒な、IDトークンを取得する、フロントエンドを作成します。
一番面倒とは言うものの、サンプルソースやSDKは用意されているので、IDとPWを入力して実行する画面を用意して、裏側の処理はサンプルソースをコピーするだけで完成します。
✅ちなみに、完成したものは、以下のリポジトリに置いてあります。
https://github.com/tamura4278/sampleFrontend4Cognito
こちらをダウンロードして実行すれば、本章は読み飛ばしても問題ありません。
🔶 Cognito SDKの取得
フロントエンドの実装方法として、SDKを使う方法と、AWS Amplifyライブラリを使う方法があるが、今回はJavascriptのSDKを使用します。
参考:Amazon Cognito ユーザープール に JavaScript アプリを追加する
JavascriptのSDKですが、直接ダウンロードする方法はありません。
ドキュメントによると、Node.jsの環境がインストールされている前提で、以下の手順で取得できます。
- 任意のフォルダで、
npm init
を実行する。 -
npm install amazon-cognito-identity-js
を実行する。 - 「node_modules」フォルダが作られ、/node_modules/amazon-cognito-identity-js/dist/amazon-cognito-identity.min.jsにSDKのライブラリが出力される。
参考:Install using separate JavaScript file
🔶 フロントエンドの作成
SDKを利用した認証処理のサンプルソースは、以下のドキュメントに記載されています。
ただし、今回はユーザーのパスワード変更も併せて行いますので、以下のドキュメントのサンプルソースも追記します。
参考:例: SDK for JavaScript で AdminCreateUser API を使用して作成されたユーザーの新しいパスワードの認証と設定
const region = 'us-east-2';
const userpoolid = 'us-east-2_aaaaaaaaa';
const clientid = 'xxxxxxxxxxx';
function callCognito() {
document.getElementById('result').value = "";
$('#message').empty();
// 画面からID/PWを取得
const username = document.getElementById('idTxt').value;
const password = document.getElementById('pwTxt').value;
const authenticationData = {
Username : username,
Password : password,
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
const poolData = {
UserPoolId : userpoolid,
ClientId : clientid
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
const userData = {
Username : username,
Pool : userPool
};
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) { // 認証成功時の処理
let accessToken = result.getAccessToken().getJwtToken();
console.log(JSON.stringify(result));
let idToken = result.idToken.jwtToken;
console.log(idToken);
$('#result').val(idToken);
},
onFailure: function(err) { // エラー発生時の処理
console.log(JSON.stringify(err));
$('#message').append(JSON.stringify(err));
},
newPasswordRequired: function(userAttributes, requiredAttributes) { // パスワード変更が必要なユーザーの、パスワードを強制変更
cognitoUser.completeNewPasswordChallenge("test9999", {}, this)
$('#message').append("パスワード変更が必要なユーザーのため、パスワードを「test9999」に変更しました\n");
}
});
}
値のセットが必要なのは先頭3行だけで、
region
は、使用しているCognitoのリージョンをセットします。
userpoolid
の値は、ユーザープールの、[全般設定]で確認できる、プールIDの値をセットします。
clientid
は、ユーザープールの、[アプリクライアント]で確認できる、アプリクライアントIDの値をセットします。
🔶 フロントエンドの動作確認
以下は、githubに置いてあるサンプルですが、フロントエンドに、IDとPWを入力して[GET id token]ボタンをクリックすると、設定に問題が無ければIDトークンが表示されます。
🔷 API Gatewayでオーサライザーの作成
🔶 オーサライザーの作成
1. API Gatewayで、新規にオーサライザーを作成する。
API Gatewayで最初に作成したAPI画面を開き、左メニューの[オーサライザー]をクリックする。
[新しいオーサライザーの作成]をクリックする。
オーサライザーの作成画面が表示されるので、以下の必要項目をセットして、[作成]をクリックします。
⬛名前
任意の名称をセットします。
⬛タイプ
今回はCognitoで認可処理を行うので、「Cognito」を選択します。
⬛Cognitoユーザープール
先程作成したユーザープール名samplePool
を選択します。
⬛トークンのソース
IDトークンは、APIリクエスト時のリクエストヘッダーにセットするが、その時のリクエストヘッダーのプロパティ名を指定します。
今回はAuthorization
とします。
⬛トークンの検証
エラーの原因になるので、今回のテストでは設定しません。
2. オーサライザーのテストを行う。
API Gatewayのオーサライザーの画面で、[テスト]をクリックする。
テストダイアログが表示されるので、「認可トークン」に値をセットして、[テスト]ボタンをクリックします。
レスポンスとしてユーザー情報が返ってきたら成功です。
⬛認可トークン
フロントエンドの動作確認で取得した、ユーザーのIDトークンをセットします。
🔶 エンドポイントにオーサライザーを設定
API Gatewayの左メニューの[リソース]をクリックして、任意のエンドポイント(今回はルートのGETメソッド)をクリックして、[メソッドリクエスト]をクリックします。
認可の値に、先程設定したオーサライザーを設定する。
オーサライザーを設定したら、[アクション] - [APIのデプロイ]をクリックして、APIをデプロイする。
これで全ての設定が終了です。
🔷 動作テスト
まずは、オーサライザーによる認可の設定を行ったエンドポイントに、リクエストを送ります。
すると、IDトークンが無いため、レスポンスとして**401 Unauthorized
が返ってきます。
次に、リクエストヘッダーにAuthorization
ヘッダーを追加して、IDトークンをセットしてリクエストを送ると、認可が通り、200 OK
**のレスポンスが返ってきます。
🔷 まとめ
以上がCognitoを使ったAPIのオーサライザーの設定の手順です。
オーサライザーの動きから理解する所から始めると、Cognitoの使い方やフロントエンド側の処理の流れなど、覚える事が多くてかなり苦戦すると思います。
それでも頑張って一通り組めば、眺めるだけで認証・認可の仕組みが理解もできるし、これを自分で全て組もうとした時の難易度の高さも理解できるし、オーサライザーの仕組みを利用する方が圧倒的に簡単だという事がわかると思います。
また、オーサライザーはCognitoを使う以外にも、Lambdaで全て自作したり、LambdaとAuth0のような外部サービスを組み合わせて構築する事もできるので、色々と試してみてください。