API Gateway カスタムオーソライザーを使って、Firebase で認証する
組み合わせただけの話なのですが、個人用メモ。
ちょっと前に「AWSによるサーバーレスアーキテクチャ」を読んだり、手元で色々試してました。本では、認証に Auth0 というサービスを使っているんですが、本が書かれた頃から Auth0 の仕様が大きく変わっています。サンプルを直すのがつらそうだったのと、その章で説明したい内容はあくまでカスタムオーソライザーの設定で、正直、認証サービスはなんでもよさそうだったので、馴染み深い Firebase Auth を代わりに使ってみました。
- 作者: 長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2018/03/14
- メディア: Kindle版
- この商品を含むブログを見る
Firebase の ID トークンの作成と確認
Firebase はクライアントサイドで認証が完結しますが、バックエンドの API サーバーでログインしているユーザーを知りたい場合があります。Firebase ではクライアントで ID トークンの発行をおこない、サーバーでこれを検証することで実現できます。手順は以下のようなかんじです。
各手順で使う Firebase の API は ID トークンを確認する | Firebase の説明がわかりやすいです。1, 2 を簡単にコードで示すと以下のようになります。
firebase.auth().currentUser.getIdToken(true) .then((idToken) => { return fetch('https://XXX.amazonaws.com/dev/get-profile', { mode: 'cors', headers: { 'Authorization': 'Bearer ' + idToken, } }); }).then((response) => { return response.text(); }).then((body) => { console.log(body); }).catch((error) => { console.error(error); });
3 は次で示します。
カスタムオーソライザーに設定する Lambda のコード
トークンの検証とポリシーの生成をおこなう Lambda 関数を用意します。あとはこの Lambda 関数を API Gateway のリクエストの認証に設定すれば終わりです。
const admin = require('firebase-admin'); const serviceAccount = require('./XXX.json'); // Firebase の管理画面からインストールできる鍵ファイル admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'https://XXX.firebaseio.com' }); const generatePolicy = (principalId, effect, resource) => { const authResponse = {}; authResponse.principalId = principalId; if (effect && resource) { var policyDocument = {}; policyDocument.Version = '2012-10-17'; policyDocument.Statement = []; var statementOne = {}; statementOne.Action = 'execute-api:Invoke'; statementOne.Effect = effect; statementOne.Resource = resource; policyDocument.Statement[0] = statementOne; authResponse.policyDocument = policyDocument; } return authResponse; }; exports.handler = function(event, context, callback){ if (!event.authorizationToken) { callback('Could not find authToken'); return; } const token = event.authorizationToken.split(' ')[1]; // ID トークンの検証 admin.auth().verifyIdToken(token) .then((decodedToken) => { const policy = generatePolicy('user', 'Allow', event.methodArn); callback(null, policy); }).catch((error) => { console.log('Failed idToken verification: ', error); callback('Authorization Failed'); }); };
つらかったこと、いまいちなこと
あんまり本質じゃないですが、CORS まわりでけっこうハマりました。
- カスタムオーソライザーでポリシーの生成にしくじっても(実行時エラーにはならないが、ポリシーの内容に問題がある場合)、API レスポンスが正常(200)で返ってくる
- エンドポイントに紐付いた Lambda 関数は実行されない
- Lambda 関数が実行できなかったらエラーを返すか、CloudWatch Logs にエラーを出力してほしい
- (自分が気づいていないだけでどこかにエラーが出ているかも)
- 「Lambda プロキシ統合の使用」を使うと、API Gateway で「CORS の有効化」をしていても、CORS 関係のヘッダーが付与されない。Lambda 関数でレスポンスを返す際に明示的に指定する必要がある
- CloudWatch Logs が見づらいのと、複数の Lambda 関数で処理を実現している場合のデバッグで、各 Lambda 関数のログを調べるのが大変
- (追記) これは S3 にログを集約して Athena で分析することになりそう