LoginSignup
31
33

More than 5 years have passed since last update.

Amazon Cognitoのワンタイムパスワード(TOTP)認証をNode.jsで試してみた

Last updated at Posted at 2018-11-13

概要

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実行のイメージ
image

そもそも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を開いたら「ユーザープールの管理」を選択し、「ユーザープールを作成する」ボタンをクリックします。

スクリーンショット_2018-11-07_11_38_01.png
スクリーンショット_2018-11-07_11_39_42.png

ユーザープールをステップに従って作成する

プール名を指定する

プール名は任意指定してください。
スクリーンショット_2018-11-07_11_42_06.png

サインイン方法を指定する

「ユーザー名」でログイン可能にします。
スクリーンショット_2018-11-07_11_46_25.png

属性を指定する

検証なので、属性指定はなしにしています。
ただし、ユーザープール後に属性の変更ができないので、実運用ではお気をつけください。
スクリーンショット_2018-11-07_11_44_45.png

パスワードなどのポリシーを指定する

初期設定のままですすめます。
スクリーンショット_2018-11-07_11_48_17.png

多要素認証を指定する

「必須」を選択します。
第2の要素に「時間ベースのワンタイムパスワード」を指定します。
スクリーンショット_2018-11-07_11_50_16.png

Eメールまたは電話番号の検証要求を指定する

今回は検証なしにしました。アラート表示されますが、今回は検証なので気にしません。「SMSメッセージの送信を許可するロール」も作成せずにすすめます。
スクリーンショット_2018-11-07_11_52_31.png

メッセージをカスタマイズする

今回は、メッセージ送信しないので、初期設定のままですすめます。
スクリーンショット 2018-11-07 11.54.41.png
スクリーンショット_2018-11-07_11_55_37.png

タグ追加、デバイス記憶の設定

両方とも初期設定のままですすめます。
スクリーンショット_2018-11-07_11_56_10.png
スクリーンショット_2018-11-07_11_56_57.png

アプリクライアントを追加する

Node.jsを利用して認証を検証するので、アプリクライアントを追加します。
スクリーンショット_2018-11-07_11_58_20.png

アプリクライアントは任意で入力してください。
「クライアントシークレットを生成」は利用しないので、外しておきます。
スクリーンショット_2018-11-07_11_59_58.png
スクリーンショット_2018-11-07_12_01_51.png

ワークフローのカスタマイズ

トリガーを使用してカスタマイズがいろいろとできますが、カスタマイズせずにすすみます。
スクリーンショット 2018-11-07 12.02.33.png
スクリーンショット_2018-11-07_12_02_45.png

設定確認して作成

ステップの最後に設定確認が表示されます。下の方に「プールの作成」ボタンがありますので、それをクリックしたら作成完了です。
スクリーンショット 2018-11-07 12.08.09.png
スクリーンショット_2018-11-07_12_08_31.png

作成完了すると、「プールID」や「プールARN」が発行されます。「プールID」はのちほど利用します。
スクリーンショット_2018-11-07_12_09_54.png

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ページでユーザープール選択→「全般設定」から確認できます。
スクリーンショット_2018-11-07_12_52_01.png

アプリクライアントのIDはユーザープール選択→「全般設定」→「アプリクライアント」から確認できます。
スクリーンショット_2018-11-07_12_54_10.png

認証部分の実装

基本的には参考にさせていただいた記事をベースにして、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

TestCognito-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 マネジメントコンソールから行います。

ユーザープールを選択して、「ユーザーとグループ」から「ユーザーの作成」ボタンをクリックします。
スクリーンショット_2018-11-07_13_22_30.png

  • ユーザー名: 必須
  • この新規ユーザーに招待を送信しますか?: チェックを外す
  • 仮パスワード: 必須
  • 電話番号: 空
  • 電話番号を検証済みにしますか?: チェックを外す
  • E メール: 空
  • E メールを検証済みにしますか?: チェックを外す

スクリーンショット_2018-11-07_13_24_05.png
スクリーンショット 2018-11-07 13.26.50.png

ユーザー作成できたら、Node.jsを実行してみます。
config.js に各IDの設定をお忘れなく(1敗

> node mfa-auth.js

先程登録したユーザ名とパスワードを入力します。
すると、シークレットコードとQRコードが出力されますので、TOTP認証に対応したスマホアプリで読み取ります。
スクリーンショット_2018-11-07_13_31_02.png

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コードを読み取り登録できると、ワンタイムパスワードが確認できるようになりますので、それを入力します。
スクリーンショット_2018-11-07_13_31_02.png

ワンタイムパスワードが正しければ、無事に認証完了し、トークンを取得することができます。
スクリーンショット_2018-11-07_13_43_30.png

ワンタイムパスワードの認証成功すると、次回からはassociateSecretCode イベントが発生せずにワンタイムパスワードの要求がされます。
スクリーンショット_2018-11-07_13_46_21.png

やったぜ。

まとめ

多要素認証(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

31
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
33