概要
Amazon Cognitoのユーザー認証で多要素認証(MFA)を有効にすると、SMSテキストメッセージによる認証ができることは知っていたのですが、時間ベースのワンタイムパスワード(TOTP)にも対応していることは知らなかったので、利用してみました。
Amazon Cognito – TOTP ソフトウェアトークン MFA
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-mfa-totp.html
Node.js実行のイメージ
そもそもCognitoとか、多要素認証、TOTPってなに?という方は下記あたりをご参考ください。
Amazon Cognitoとは
Amazon Cognito とは
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html
Amazon Cognito は、ウェブアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートしています。 ユーザーは、ユーザー名とパスワードを使用して直接サインインするか、Facebook、Amazon、Google などのサードパーティーを通じてサインインできます。
多要素認証(Multi-Factor Authentication)とは
多要素認証 – Wikipedia
https://ja.wikipedia.org/wiki/%E5%A4%9A%E8%A6%81%E7%B4%A0%E8%AA%8D%E8%A8%BC
多要素認証(たようそにんしょう)は、アクセス権を得るのに必要な本人確認のための要素(証拠)を複数、ユーザーに要求する認証方式である。 必要な要素が二つの場合は、二要素認証や二段階認証とも呼ばれる。
やはりお前らの多要素認証は間違っている
https://dev.classmethod.jp/etc/multi-factor-authentication/
前述の通り、コンピュータの世界では knowledge factor を使った認証が一般的です。 しかし昨今、パスワードが流出したり、簡単すぎるパスワードを推測されたり、という事故が多発しています。 従って、そのような事故があっても直ちに損害が出ないように、2つの要素を組み合わせて認証を行うシステムがあります。これを2要素認証と呼びます。
今さら聞けない2段階認証の話いろいろ
https://qiita.com/isaoshimizu/items/5ca25efebdc5ecee7d9b
ここ数年、2段階認証(2要素認証、Two Factor Authencication)に対応したサービスがどんどん増えてきています。2段階認証は、IDとパスワードだけでなく、ユーザー専用のコード(数値)を入力することでセキュリティを強化するというもの。
時間ベースのワンタイムパスワード(Time-based One Time Password)
今すぐできる、Webサイトへの2要素認証導入
https://blog.ohgaki.net/use-2-factor-authentication-with-your-web-sites
TOTPは名前の通りシードと時間をベースにパスワードを生成します。時間と共にパスワードは変化します。
2要素認証のTOTPとHOTP、どちらがより安全か?
https://blog.ohgaki.net/2fa-totp-hotp-which-is-safer
前提
- AWSアカウントがありAmazon Cognitoが利用できる
- Node.jsとnpmがインストールされている
Amazon Cognitoでユーザープールを作成する
こちらの記事を参考にさせていただき、MFAを有効にしたユーザープールを作成します。
javascriptでCognitoユーザプール認証
https://qiita.com/tamo_breaker/items/2cba901565a4fe9dff1a
AWS マネジメントコンソールでAmazon Cognitoを開く
Amazon Cognitoを開いたら「ユーザープールの管理」を選択し、「ユーザープールを作成する」ボタンをクリックします。
ユーザープールをステップに従って作成する
プール名を指定する
プール名は任意指定してください。
サインイン方法を指定する
「ユーザー名」でログイン可能にします。
属性を指定する
検証なので、属性指定はなしにしています。
ただし、ユーザープール後に属性の変更ができないので、実運用ではお気をつけください。
パスワードなどのポリシーを指定する
初期設定のままですすめます。
多要素認証を指定する
「必須」を選択します。
第2の要素に「時間ベースのワンタイムパスワード」を指定します。
Eメールまたは電話番号の検証要求を指定する
今回は検証なしにしました。アラート表示されますが、今回は検証なので気にしません。「SMSメッセージの送信を許可するロール」も作成せずにすすめます。
メッセージをカスタマイズする
今回は、メッセージ送信しないので、初期設定のままですすめます。
タグ追加、デバイス記憶の設定
両方とも初期設定のままですすめます。
アプリクライアントを追加する
Node.jsを利用して認証を検証するので、アプリクライアントを追加します。
アプリクライアントは任意で入力してください。
「クライアントシークレットを生成」は利用しないので、外しておきます。
ワークフローのカスタマイズ
トリガーを使用してカスタマイズがいろいろとできますが、カスタマイズせずにすすみます。
設定確認して作成
ステップの最後に設定確認が表示されます。下の方に「プールの作成」ボタンがありますので、それをクリックしたら作成完了です。
作成完了すると、「プールID」や「プールARN」が発行されます。「プールID」はのちほど利用します。
Node.jsで実装する
Node.jsによる実装もこちらの記事で紹介されている実装をベースにさせてもらいました。
javascriptでCognitoユーザプール認証
https://qiita.com/tamo_breaker/items/2cba901565a4fe9dff1a
実装はGitHubにアップしていますので、よろしければご参考ください。
https://github.com/kai-kou/use-cognito-totp-mfa
環境設定とライブラリのインストール
> node -v v10.11.0 > npm -v 6.4.1 > mkdir 任意のディレクトリ > cd 任意のディレクトリ > npm init > npm install --save node-fetch > npm install --save amazon-cognito-identity-js > npm install --save prompt > npm install --save qrcode-terminal
CognitoへのアクセスにAWS JavaScript SDK(amazon-cognito-identity-js)を利用します。ユーザー名、パスワード、ワンタイムパスワードを入力するのにはprompt、QRコードを生成・表示するのにqrcode-terminalを利用しています。
amazon-archives/amazon-cognito-identity-js
https://github.com/amazon-archives/amazon-cognito-identity-js
flatiron/prompt
https://github.com/flatiron/prompt
gtanner/qrcode-terminal
https://github.com/gtanner/qrcode-terminal
実装
> touch config.js > touch mfa-auth.js
設定ファイル
CognitoのユーザープールIDとアプリクライアントのIDを設定します。
config.js
module.exports = { UserPoolId: '[CognitoのユーザープールID]', ClientId: '[アプリクライアントのID]' }
ユーザープールIDはAWS マネジメントコンソールのCognitoページでユーザープール選択→「全般設定」から確認できます。
アプリクライアントのIDはユーザープール選択→「全般設定」→「アプリクライアント」から確認できます。
認証部分の実装
基本的には参考にさせていただいた記事をベースにして、cognitoUser.authenticateUser
にMFA時に発生するイベントを追加して、認証成功時にトークンが取得できるところまでを実装しています。
MFAに関する実装には公式ドキュメントを参考にしました。
Amazon Cognito – 例: JavaScript SDK の使用
「MFA メソッドを選択し、TOTP MFA を使用して認証する」
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
mfa-auth.js
global.fetch = require('node-fetch') const AmazonCognitoIdentity = require('amazon-cognito-identity-js'); const prompt = require('prompt'); const qrcode = require('qrcode-terminal'); const config = require('./config'); login = function () { prompt.start(); let prompt_schema = { properties: { username: { required: true }, password: { hidden: true } } }; prompt.get(prompt_schema, function (err, result) { let username = result['username']; let password = result['password']; // ユーザープールID、アプリクライアントIDの設定 let poolData = { UserPoolId: config.UserPoolId, ClientId: config.ClientId }; // ユーザーの設定 let userData = { Username: username, Pool: new AmazonCognitoIdentity.CognitoUserPool(poolData) }; let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData); // 認証情報の設定 let authenticationData = { Username: username, Password: password }; let authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData); // Cognitoに認証を要求 cognitoUser.authenticateUser(authenticationDetails, { onSuccess: function (result) { // 認証成功時に発生します console.log('onSuccess'); console.log('認証成功時に発生します'); let idToken = result.getIdToken().getJwtToken(); console.log('トークン取得できました^^'); console.log(idToken); }, newPasswordRequired: function (userAttributes, requiredAttributes) { // ユーザー作成後、初回ログイン時にパスワード変更が必要になる // とりあえず、仮パスワードをそのまま設定しています console.log('ユーザー作成後、初回ログイン時にパスワード変更が必要'); cognitoUser.completeNewPasswordChallenge(password, {}, this); }, mfaSetup: function (challengeName, challengeParameters) { // ユーザープールでMFAが有効化されていると発生します console.log('mfaSetup'); console.log('ユーザープールでMFAが有効化されていると発生します'); cognitoUser.associateSoftwareToken(this); }, associateSecretCode: function (secretCode) { // MFA有効化されていてTOTP初回認証時に発生します // SecretCodeが発行されるので、Google Authenticatorなどに登録できるよう // QRコードを生成します。(シークレットコードを手動入力するのもあり) console.log('associateSecretCode'); console.log('MFA有効化されていてTOTP初回認証時に発生します'); // QRコードを生成してターミナルに表示 let url = 'otpauth://totp/Test?secret=' + secretCode + '&issuer=Cognito-TOTP-MFA'; console.log('シークレットコード: ' + secretCode); qrcode.generate(url, {small: true}); let _this = this; let getValue = 'Google AuthenticatorでQRコードを読み取り、ワンタイムパスワードを入力してください'; prompt.get([getValue], function (err, result) { challengeAnswer = result[getValue]; cognitoUser.verifySoftwareToken(result[getValue], 'My TOTP device', _this); console.log('2回目からはQRコードの読み取りは不要です'); }); }, totpRequired: function (secretCode) { // ワンタイムパスワード要求時に発生 // Google Authenticatorなどからワンタイムパスワードを入力して認証します console.log('totpRequired'); console.log('ワンタイムパスワード要求時に発生'); let _this = this let getValue = 'Google Authenticatorのワンタイムパスワードを入力してください'; prompt.get([getValue], function (err, result) { var challengeAnswer = result[getValue]; cognitoUser.sendMFACode(challengeAnswer, _this, 'SOFTWARE_TOKEN_MFA'); }); }, onFailure: function (err) { console.log('onFailure'); console.log('認証失敗時に発生します'); console.log(err); } }); }); }; login();
cognitoUser.authenticateUser
メソッドのイベント
メソッドを実行すると、ユーザー名とパスワードによる認証から始まり、必要に応じてイベント?が発生する流れになります。
newPasswordRequired
: 初回ログイン時のパスワード変更要求mfaSetup
: MFA有効時に発生associateSecretCode
: TOTP認証の初回時に発生totpRequired
: ワンタイムパスワード要求時に発生onFailure
: 認証失敗時に発生
他にもselectMFAType
というのがありますが、これはSMSテキストメッセージによる認証やTOTP認証など要素が複数ある場合に、発生するみたいですが、今回はTOTP認証のみなので、割愛しています。
TOTP対応アプリ用にQRコード生成
QRコード生成に必要となるURIフォーマットについては下記を参考にさせてもらいました。
二段階認証(TOTP)メモ
https://qiita.com/xylitol45@github/items/4f8418554a6550189341
Test
、Cognito-TOTP-MFA
は任意で設定可能です。
mfa-auth.js(抜粋)
// QRコードを生成してターミナルに表示 let url = 'otpauth://totp/Test?secret=' + secretCode + '&issuer=Cognito-TOTP-MFA';
prompt利用時の注意点
イベント処理にprompt.get
を利用する場合、cognitoUser
のメソッドにthis
パラメータを直接渡すと、UnhandledPromiseRejectionWarning: TypeError: callback.onFailure is not a function
とエラーが発生するので、ご注意ください。非同期怖いです。
mfa-auth.js(抜粋)
let _this = this; let getValue = 'Google AuthenticatorでQRコードを読み取り、ワンタイムパスワードを入力してください'; prompt.get([getValue], function (err, result) { challengeAnswer = result[getValue]; // thisを直接渡すとエラーになる cognitoUser.verifySoftwareToken(result[getValue], 'My TOTP device', _this); console.log('2回目からはQRコードの読み取りは不要です'); });
ユーザー登録と認証
Cognitoのユーザープールにユーザーを登録して、実際に認証してみます。今回は手抜きでユーザー登録はAWS マネジメントコンソールから行います。
ユーザープールを選択して、「ユーザーとグループ」から「ユーザーの作成」ボタンをクリックします。
- ユーザー名: 必須
- この新規ユーザーに招待を送信しますか?: チェックを外す
- 仮パスワード: 必須
- 電話番号: 空
- 電話番号を検証済みにしますか?: チェックを外す
- E メール: 空
- E メールを検証済みにしますか?: チェックを外す
ユーザー作成できたら、Node.jsを実行してみます。config.js
に各IDの設定をお忘れなく(1敗
> node mfa-auth.js
先程登録したユーザ名とパスワードを入力します。
すると、シークレットコードとQRコードが出力されますので、TOTP認証に対応したスマホアプリで読み取ります。
TOTP認証に対応した認証アプリには「Google Authenticator」などがあります。
App Store – iTunes – Apple
https://itunes.apple.com/jp/app/google-authenticator/id388497605?mt=8
Google Play
https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ja
QRコードを読み取り登録できると、ワンタイムパスワードが確認できるようになりますので、それを入力します。
ワンタイムパスワードが正しければ、無事に認証完了し、トークンを取得することができます。
ワンタイムパスワードの認証成功すると、次回からはassociateSecretCode
イベントが発生せずにワンタイムパスワードの要求がされます。
やったぜ。
まとめ
多要素認証(MFA)の実装はややこしくて、大変そうなイメージがありましたが、Amazon Cognitoを利用すると比較的簡単に実装することができました。
ユーザープールの設定で、ユーザーごとに任意選択させることや、SMSテキストメッセージ認証を追加することもできるので、アプリクライアントに対して、よりセキュアな認証を実装することができますね^^
参考
Amazon Cognito – TOTP ソフトウェアトークン MFA
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-mfa-totp.html
Amazon Cognito とは
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html
多要素認証 – Wikipedia
https://ja.wikipedia.org/wiki/%E5%A4%9A%E8%A6%81%E7%B4%A0%E8%AA%8D%E8%A8%BC
やはりお前らの多要素認証は間違っている
https://dev.classmethod.jp/etc/multi-factor-authentication/
今さら聞けない2段階認証の話いろいろ
https://qiita.com/isaoshimizu/items/5ca25efebdc5ecee7d9b
今すぐできる、Webサイトへの2要素認証導入
https://blog.ohgaki.net/use-2-factor-authentication-with-your-web-sites
2要素認証のTOTPとHOTP、どちらがより安全か?
https://blog.ohgaki.net/2fa-totp-hotp-which-is-safer
javascriptでCognitoユーザプール認証
https://qiita.com/tamo_breaker/items/2cba901565a4fe9dff1a
Amazon Cognito – 例: JavaScript SDK の使用
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
amazon-archives/amazon-cognito-identity-js
https://github.com/amazon-archives/amazon-cognito-identity-js
flatiron/prompt
https://github.com/flatiron/prompt
gtanner/qrcode-terminal
https://github.com/gtanner/qrcode-terminal
二段階認証(TOTP)メモ
https://qiita.com/xylitol45@github/items/4f8418554a6550189341
App Store – iTunes – Apple
https://itunes.apple.com/jp/app/google-authenticator/id388497605?mt=8
Google Play
https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ja