今さら感ありますがApplication Load Balancerでのユーザー認証機能を試してみました。
JWTの検証で意外にハマってしまったのでメモしておきます。
IdPの設定
今回はOneLoginを利用しました。
用意されている「OpenId Connect app」をCompany Appとして追加すれば終わりと高を括っていたところ、ALBがHTTP 561を返し続け大分ハマりました。
トークンエンドポイントへのリクエストを「Basic」ではなく「POST」に設定しないといけないのでした。1
ALBの設定
AWSのドキュメントとブログを頼りに設定できました。
- Application Load Balancer を使用してユーザーを認証する(AWSドキュメント)
- Application Load Balancer 組み込み認証によりログインを簡略化(Amazon Web Services ブログ)
個人的なポイントとしては以下。
- ALBはHTTPSでアクセスされるように設定する
- 認証アクションで入力を求められる各エンドポイントは
/.well-known/openid-configuration
から確認する2 - リダイレクトURLには
https://<ALBのドメイン名>/oauth2/idpresponse
を設定する
アプリケーション実装
上で挙げたブログ曰く、
しかしながら、対象リクエストが改ざんされていないことを担保する JWT ヘッダー上の署名を検証するためのアプリケーションを実装することは依然として重要です。
ということなので、Auth0のnode-jsonwebtokenを利用して検証を行うことにしました。が、ダメ。デコードすらできず。IDトークンがbase64urlエンコードされていないっぽい。
Base64エンコードした後、以下の変換を施したものがbase64urlエンコードらしい3のですがIDトークンに「=」が入ってしまっていました。削除するとデコードできるようになりましたがverifyは失敗します。
対象の文字 | 変換後 |
---|---|
= | 削除する |
+ | - |
/ | _ |
そこでjwt.ioにて他のライブラリがないか探しjsrsasignというライブラリを使わせてもらうことにしました。
これで検証とデコードが問題なくできるようになり、あとはAWSのドキュメントに載っているPythonで書かれたサンプルをJavaScriptに移植すれば、出来上がりっ
const express = require('express');
const fetch = require('node-fetch');
const rs = require('jsrsasign');
const app = express();
app.get('/activate', (req, res) => {
// IDトークンを取得
const token = req.header('x-amzn-oidc-data');
// ヘッダーからkey idを取得
const decodedJwt = rs.KJUR.jws.JWS.parse(token);
const kid = decodedJwt.headerObj.kid;
// 検証用の公開鍵を取得
fetch(`https://public-keys.auth.elb.ap-northeast-1.amazonaws.com/${kid}`).then(response => {
return response.text();
}).then(pem => {
// トークンを検証
const pubKey = rs.KEYUTIL.getKey(pem);
const isValid = rs.KJUR.jws.JWS.verify(token, pubKey, ["ES256"]);
if (isValid) {
res.send('valid');
} else {
res.status(400).send('invalid');
}
});
});
app.get('/', (req, res) => {
res.send('arrived!');
});
app.listen(3000);