概要
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
そもそも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を設定します。
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
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
は任意で設定可能です。
// 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
とエラーが発生するので、ご注意ください。非同期怖いです。
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