API Gatewayのカスタム認証でワンタイムパスワードを使ってみた
概要
API Gatewayの認証でなんとなく(時間ベースの)ワンタイムパスワード(以後、TOTP
)を使いたくなったので作ってみる。
基本的にnodejsを使用して構築する。
認証方法
API Gatewayのカスタム認証でTOTPの認証を行う。
TOTPのライブラリにはSpeakeasy(GitHub)を使う。
また、Google Authenticatorなどに登録するためのQRコードの生成にはQRCode Terminal Edition(GitHub)を使用する。
構築
TOTP用の鍵(?)とAuthenticator登録用のQRコードを生成する
まずは、必要なライブラリをインストールする。
$ mkdir make-key $ cd make-key $ npm install speakeasy $ npm install qrcode-terminal
インストールしたら、鍵を作りQRコードを表示するgenerate.js
を作成する。
"use strict"; const fs = require('fs'); // 鍵のテキストを保存する const speakeasy = require('speakeasy'); const qrcode = require('qrcode-terminal'); // TOTPの鍵の情報を生成する const secret = speakeasy.generateSecret({length: 20}); // TOTPの鍵をテキスト保存する const key = secret.base32; fs.writeFile("totp_secret_base32.txt", key); // 登録用QRコードを表示する。 const url = secret.otpauth_url; qrcode.generate(url);
実行すると以下のようになる。 (Warningが出ているが気にしない) このQRコードが登録用のものになる。 TOTPの鍵は次のように作られている。
$cat totp_secret_base32.txt KJ4DGPZ4LM7DU2KWPVOV222YLNEFKJLT
この値は次のカスタム認証用Lambda関数で使用する。
カスタム認証用Lambda関数の作成
こちらもまずはライブラリのインストールを行う。
$ mkdir auth-method $ cd auth-method $ npm install speakeasy
次に認証用のLambda関数index.js
を書きます。
'use strict'; const speakeasy = require('speakeasy'); const generatePolicy = function(principalId, isAllow, resource) { const effect = isAllow? "Allow": "Deny"; // IAM ポリシーを生成する return { principalId: principalId, policyDocument: { Version: '2012-10-17', Statement: [{ Action: 'execute-api:Invoke', Effect: effect, Resource: resource }] } }; }; exports.handler = (event, context, callback) => { // トークンを取得 const token = event.authorizationToken; const key = process.env.TOTP_SECRET; const result = speakeasy.totp.verify({ secret: key, encoding: 'base32', token: token, window: 2 }); if (result){ callback(null, generatePolicy('user', true, event.methodArn)); }else{ callback(generatePolicy('user', true, event.methodArn)); } };
Lambdaに登録するためのzipファイルsource.zip
を作成します。
$ zip -r source.zip index.js node_modules/
このsource.zipをLambda関数作成時にアップロードします。
この際、環境変数を設定します。
キー"TOTP_SECRET“にtotp_secret_base32.txtの中身の文字列を入力します。
Lambda関数の作成ができました。
API Gatewayにカスタム認証を設定する
API Gatewayで新しいAPIを作成する(ここは既存のAPIでも問題はない)。
ここでは"totp“という名前で作成したことにする。
APIを作成したら左端のカラムを下にスクロールさせて、作成したAPIのオーソライザーを選択します。
Lambda関数のリージョンや名前などを入力し作成ボタンを押します。
(キャッシュの仕組み等は検証してないので結果のTTLを”0“に設定しています)
作成すると下にオーサライザーのテストというものができるので、IDトークンにワンタイムパスワードを入れてどういう動きになるかを見るといいかもしれません。
次に、APIのリソースを作成します(リソースの作成は、左端のカラムのtotpのリソースです)。
ここでは、
- CORSを有効にした
get
リソースを作成し、 get
リソースにGETメソッドを作成し、統合タイプをMock
にする。
ということにします。
メソッドリクエストをクリックし、認証に作成したオーソライザーを選択します。
オーソライザーを設定したら、APIをデプロイします。
これでAPIの構築は完了です。
curlで叩いてみる
curlコマンドで叩いてみます。
最初はワンタイムパスワードを渡してないので当然失敗しています。
二度目は正規のワンタイムパスワードを渡しているので成功しています。
やってみて
結局のところは、カスタム認証に使うLambda関数の分岐条件にワンタイムパスワードを使うだけなので検証前のワクワク感がすぐなくなってしまいました。
これにユーザ認証まで噛ませようとすると、JSON文字列を渡してパースするほうがいいのかなぁとも思うところです。
個人用途のちょっとした認証だったら便利そうです。