概要
Firebase Authenticationに登録されているユーザーだけがAPIを使えるようにするため、
エンドポイントであるAPI Gatewayにオーソライザーを設定します。
処理の流れは以下の通りです。
- Firebase Authentication SDK を使用し IDトークン を取得する
- 取得したIDトークンをヘッダーに付与し、APIGatewayのエンドポイントへリクエストを送る
- API Gatewayの オーソライザー が Firebase Admin SDK を使用しIDトークンを検証する
- 認証できた場合、Lambda関数を実行する
対象読者
Firebase Authenticationの機能が分かる方
API GatewayとLambdaでAPIを作成する方法が分かる方
APIの作成
テスト用のシンプルなAPIをAPI GatewayとLambdaで作成しておきます。
例として、Lambda関数は以下の自動生成されるコードをそのまま使用します。
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
API Gatewayを新規に作成した後、GET
メソッドを付与しLambda関数と紐付け、APIを作成します。
オーソライザーの作成
作成したAPI Gatewayに付与するオーソライザーを実装します。
- Firebaseプロジェクトの秘密鍵を取得
- Lambda Layerを作成
- 認証用のLambda関数の実装
- オーソライザーの設定
の順で解説していきます。
Firebaseプロジェクトの秘密鍵を取得
Firebaseプロジェクトを開き、サイドメニューの歯車アイコンから[プロジェクトの設定]を開きます。
[サービスアカウント]を開き、[新しい秘密鍵の生成]をクリックして秘密鍵(jsonファイル)をダウンロードします。
Firebaseプロジェクトの秘密鍵が取得できました。
Lambda Layerを作成
Lambda関数内でFirebase Admin SDK
を使用するためにLayerを作成していきます。
PC上にfirebase_adminフォルダを作成します。
$ mkdir firebase_admin
firebase_adminフォルダに移動しfirebase-admin
をインストールします。
$ cd firebase_admin
$ npm install firebase-admin
firebase_adminフォルダをfirebase_admin.zipという名前でzip化します。
$ cd ..
$ zip -r firbase_admin.zip firebase_admin
AWSマネジメントコンソール画面でLambdaのレイヤーを選択します。
新規Layerをランタイム Node.js 14.xで、firebase_admin.zipをS3経由でアップロードし作成します。
認証用のLambda関数を作成
新規Lambda関数をランタイム Node.js 14.xで作成します。
レイヤーを追加
[Layers]を選択し、[レイヤーの追加]から先ほど作成したレイヤーを追加します。
秘密鍵を追加
Lambda関数のルートディレクトリ直下に、
Firebaseプロジェクトの秘密鍵のjsonファイルをadmin.json
としてアップロードします。
Lambda関数の実装
Lambda関数本体のコードは以下の通りです。
//SDKを初期化
const admin = require('firebase-admin');
const serviceAccount = require('./admin.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
})
//IDトークンの検証
exports.handler = async function(event, context, callback) {
const uid = await admin.auth().verifyIdToken(event.authorizationToken)
.then((decodedToken) => {
return decodedToken.uid;
})
.catch((error) => {
console.log(error);
return null;
})
if (uid) {
callback(null, generatePolicy('user', 'Allow', event.methodArn));
} else {
callback(null, generatePolicy('user', 'Deny', event.methodArn));
}
};
//IAMポリシーを生成する関数を定義
var generatePolicy = function(principalId, effect, resource) {
var 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;
}
以下、このコードについてパートごとに解説します。
SDKを初期化
serviceAccount
にはadmin.json
を読み込みます。
const admin = require('firebase-admin');
const serviceAccount = require('./admin.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
})
IAMポリシーを生成する関数を定義
以下の関数でIAMポリシーを生成しAPI Gatewayに返却します。
このIAMポリシーのeffect
が'Allow'
の場合のみ、APIのLambda関数の実行を許可します。
principalId
は便宜的なユーザーの識別値で、methodArn
は、受信するメソッドリクエストの ARN です。
var generatePolicy = function(principalId, effect, resource) {
var 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;
}
IDトークンの検証
firebase-admin
のverifyIdToken
関数でIDトークンの検証を行います。
検証に成功した場合は、アカウント毎に設定されているuidを取得できます。
uidがある場合はLambda関数の実行を許可するIAMポリシーを、
ない場合は実行を拒否するIAMポリシーを生成し返却します。
exports.handler = async function(event, context, callback) {
const uid = await admin.auth().verifyIdToken(event.authorizationToken)
.then((decodedToken) => {
return decodedToken.uid;
})
.catch((error) => {
console.log(error);
return null;
})
if (uid) {
callback(null, generatePolicy('user', 'Allow', event.methodArn));
} else {
callback(null, generatePolicy('user', 'Deny', event.methodArn));
}
};
オーソライザーを設定
作成した認証用のLambda関数をAPI Gatewayのオーソライザーに設定します。
オーソライザーの作成
認証を適用したいAPI Gatewayを選択後、
[オーソライザー] →[新しいオーソライザーの作成]を選択し、 以下の通り作成します。
- 名前:任意
- タイプ:Lambda
- Lambda関数:作成した認証用のLambda関数
- Lambda呼び出しロール:空白のまま
- Lambdaイベントペイロード:トークン
- トークンのソース:Authorization
- トークンの検証:空白のまま
トークンのソースはAuthorization
とすることで、
後で設定するCORSを有効化した際にデフォルトで許可されるヘッダーになります。(他の値だと手動で設定する必要があります。)
認可のキャッシュを有効にするとmethodArn
もキャッシュされるため、
TTL内に別のリソースでオーソライザーを呼び出す場合は食い違いによるエラーが発生します。
そのためオーソライザーを使い回したい場合はmethodArn
をワイルドカードで指定するか、認可のキャッシュを無効にします。
【AWS】API Gateway Lambdaオーソライザー「User is not authorized to access this resource」エラーの原因と対応
メソッドリクエストへオーソライザーを紐付け
[リソース]→[/ の GET]→[メソッドリクエスト]を選択します。
[設定]の[認可]のプルダウンから作成したオーソライザーを選択し、横のチェックマークをクリックします。
APIのデプロイ
[アクション]から[CORSの有効化]を選択すると設定画面が表示されるため、
そのまま[CORSを有効にして既存のCORSを置換]をクリックします。
[アクション]→[APIのデプロイ]から、APIをデプロイします。
リクエストの送信
クライアント側で以下の例のような処理を適宜実装してください。
'Hello from Lambda!'
がコンソールに出力されれば成功です。
Firebase認証を行い、IDトークンを取得する情報は多くあるため詳細は省きます。
firebase.auth().onAuthStateChanged(async user => {
if (user) {
const authToken = await user.getIdToken()
const headers = {
'Authorization': authToken
}
//API Gatewayのエンドポイントを設定
const url = "https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/"
response = axios.get(url, {headers: headers})
console.log(JSON.parse(response.data.body))
}
})
最後に
Firebase Authenticationを使ってログイン・認証処理を実装している場合は、
それを利用し簡単にAPI Gatewayに認証機能をつけることができました。
1つオーソライザーを作れば他のリソースやメソッドでも使い回せるので便利ですね。
それでは最後までご覧いただきありがとうございました。 コードの不備やご意見等あればコメントいただけますと幸いです。