通常、認証確認処理はwebサーバーでやることが多いと思いますが、
グローバルサイトでかつ同時に多量のアクセスが考えられる場合、lambdaEdgeで認証チェックを実装すると
静的なサイト全体に認証がかけられ、かつWEBサーバーにアクセス負荷をかけずに認証確認処理を実装できます。
今回は、cognitoから発行されたid_tokenをでコードして検証するコードをご紹介します。
#Lambda関数でのnpm moduleの利用法
今回、実装するにあたって
- jsonwebtoken
- jwk-to-pem
これら2つのモジュールを利用しています。これらはnpmでinstallを行うのですが、Lambdaコンソール上ではもちろんnpmは実行できません。
このため、まずはlambdaのコンソールからアクション>関数のエクスポートでデプロイパッケージをDLしてください。
DLできたら解凍し、そこにpackage.jsonを追加するなどしてnpm installで上記の2つのモジュールをinstallしてください。
開発についてもローカル環境でエディタを使って行う方が圧倒的に効率的だと思います。
編集完了後は、再度zip圧縮を行いアップロード元から.zipファイルを選択し反映すればよいのですが、
この時、ディレクトリを圧縮するのではなく、エントリポイントになるindex.jsファイルがルート階層に来るように圧縮を行ってください。
#必要な情報
実装するにあたり
- userPoolId
- appClientId
- jwks.json
が必要になります。
cognitoにclientアプリ登録を行うなどして取得しておきます。
#cookieの取得
今回はcookieにid_tokenなどが格納されてくる前提で実装を行います。
まずは、requestから必要なcookieを取得します。
cookieの文字列中にCognitoIdentityServiceProviderの文字列が入っているものを探し、そのcookie文字列をパースしています。
※もし違うnameでcookieに格納している場合は検索する文字列を変更する必要があります。
function parseCookies(request) {
var list = {};
var headers = request.headers;
if (Object.keys(headers).indexOf('cookie') === -1) {
return list;
}
var rc = false;
for (let i = 0; i < headers.cookie.length; i++) {
if (headers.cookie[i].value.indexOf("CognitoIdentityServiceProvider") >= 0) {
rc = headers.cookie[i].value;
break;
}
}
if (!rc) {
return list;
}
rc.split(';').forEach(function (cookie) {
var parts = cookie.split('=');
var key = parts.shift().trim();
list[key] = decodeURI(parts.join('='));
});
return list;
}
#jwksからpemへの変換
トークンをデコードするにあたり、jwksをpemに変換する必要があります。
npm installしたjwk-to-pemを利用し、jwksをpemに変換しましょう。
var jwk = JSON.parse(fs.readFileSync('./jwks.json', 'utf8'));
var pem = jwkToPem(jwk.keys[0]);
#id_tokenをデコードして、各種値を検証する
cookieからid_tokenの値を取得し、それをjwksから変換したpemを使いデコードします。
デコードにはjsonwebtokenを利用しています。
jwt.verify(id_token, pem, { algorithms: ['RS256'] }, function (err, decodedToken) {
if (err) {
// デコードエラー(id_tokenの期限切れはここに入ります)
// TODO:認証ページなどへのリダイレクトレスポンスを返す
return;
}
// Audience (aud) 確認
if (decodedToken.aud !== appClientId) {
// clientIDの不一致(期限切れはここに入ります)
// TODO:認証ページなどへのリダイレクトレスポンスを返す
return;
}
// Issuer (iss) 確認
if (decodedToken.iss !== 'https://cognito-idp.us-east-1.amazonaws.com/' + userPoolId) {
// userPoolIDの不一致(期限切れはここに入ります)
// TODO:認証ページなどへのリダイレクトレスポンスを返す
return;
}
// token_use 確認(今回はid)
if (decodedToken.token_use !== 'id') {
// userPoolIDの不一致(期限切れはここに入ります)
// TODO:認証ページなどへのリダイレクトレスポンスを返す
return;
}
});
今回はこれらのチェックが通ったものを認証済みとして扱い、それ以外の場合は認証ページへのリダイレクトレスポンスを返します。
#まとめ
本来はrefresh_tokenを使い、id_tokenのリフレッシュまで実装するとなおよいかもしれませんが、
lambdaEdgeで複雑な処理を行うとエラーハンドリングが大変かつコストもかかってしまうので、
そのあたりはclientで実装してもらったりすると良いかと思います。