こんにちは。現場の最前線でセキュリティと格闘しているエンジニアの皆さん、お疲れ様です。
今日は「OpenID Connect(OIDC)」という、今のWeb開発では避けて通れない「IDの受け渡し」についてお話しします。難しそうな名前ですが、要は「オンライン上の身分証明書(IDトークン)をどうやって偽物と見分けるか」というお話です。
「JWT(JSON Web Token)をとりあえず受け取ってデコードして中身を見るだけ」……そんな実装をしていませんか? それ、実は家の鍵を「鍵の形をしたプラスチックの板」で開けようとしているのと同じくらい危うい状態です。
一緒に、堅牢な門構えを作っていきましょう。
—
1. IDトークンは「特殊な封印がされた手紙」
IDトークンは、認可サーバー(GoogleやAuth0など)が発行する「あなたは〇〇さんです」という証明書です。しかし、ネット上では誰でも偽造の手紙を書けてしまいます。だからこそ、「本当に発行元が書いたものか?」を確認する「署名検証」が不可欠です。
これを身近な例で例えると:
- 署名: 封筒の裏にある「特殊なシーリングワックス(封蝋)」。剥がすとバレるし、誰でも真似できるわけではない。
- 公開鍵(JWKS): 「このワックスの柄(ハンコ)は、このお店(認可サーバー)の公式のものですよ」と公開されている見本。
署名検証を飛ばすことは、「封筒が開封されていても、中身が書き換えられていても気にしない」と言っているのと同じです。泥棒が一番喜ぶ状態ですね。
—
2. 署名検証の厳密な手順:JWKSを活用する
「公開鍵をソースコードに直書き」している人はいませんか? 鍵は定期的に入れ替わるものです。ハードコーディングは避け、JWKS(JSON Web Key Set)エンドポイントから動的に取得するのがプロの作法です。
実装のステップ
1. メタデータ取得: `/.well-known/openid-configuration` を叩いて、鍵の置き場(`jwks_uri`)を確認する。
2. 鍵の取得・キャッシュ: JWKSから公開鍵を取得し、メモリに一定時間キャッシュする(毎回通信するとパフォーマンスが悪化します)。
3. 検証: トークンのヘッダーにある `kid`(鍵のID)と一致する公開鍵を使って、署名を検証する。
—
3. なぜ「中身のチェック」が必要なのか?(iss, aud, exp, nonce)
署名が本物だとわかっても、中身が正しくなければ意味がありません。以下の4つは、最低限の「防犯センサー」です。
- iss (Issuer): 「誰が発行したか」。Googleが発行したはずなのに、謎のサーバーから届いていたらアウトですよね。
- aud (Audience): 「誰宛てか」。あなたが作ったアプリ宛てである必要があります。Aさんのアプリ宛てのトークンを、悪意あるBさんが自分のアプリに流用する「なりすまし攻撃」を防ぎます。
- exp (Expiration Time): 「有効期限」。古い鍵を拾った泥棒に、いつまでも侵入されないようにするための時限装置です。
- nonce: 「一度限りの使い回し防止コード」。ログインのたびに生成する乱数です。これが一致しないなら、「昔のログイン記録を盗み見て再送した(リプレイ攻撃)」可能性があります。
—
4. コードで見る「守りのポイント」
Node.jsなどでよく使われるライブラリを想定した、検証のイメージコードです。
const jwt = require(‘jsonwebtoken’);
const jwksClient = require(‘jwks-rsa’);
// 1. 公開鍵を取得するクライアントのセットアップ
const client = jwksClient({
jwksUri: ‘https://your-auth-provider.com/.well-known/jwks.json’
});
// 2. トークン検証関数
async function verifyToken(token) {
// トークンのヘッダーから鍵のID(kid)を取り出す
const decoded = jwt.decode(token, { complete: true });
const key = await client.getSigningKey(decoded.header.kid);
const publicKey = key.getPublicKey();
// 3. 署名とクレームを厳密に検証
return jwt.verify(token, publicKey, {
issuer: ‘https://your-auth-provider.com’, // issの確認
audience: ‘your-client-id’, // audの確認
algorithms: [‘RS256’] // アルゴリズムの固定(必須!)
});
}
ここがポイント!
- algorithmsの固定: ここを書き忘れると、「署名なし(none)」や、公開鍵を秘密鍵として悪用する攻撃(`alg: HS256`への強制変更)を許してしまいます。必ず `[‘RS256’]` などに限定しましょう。
—
最後に:セキュリティは「疑うこと」から始まる
セキュリティは、一度設定して終わりではありません。相手(認可サーバー)の仕様変更、ライブラリの脆弱性、そして攻撃者の手法……これらは常に進化しています。
今回紹介した「署名検証」と「クレームの確認」は、いわば家の玄関の二重ロックです。ここがしっかりしていれば、多少のトラブルがあっても被害を最小限に抑えられます。
まずはご自身の開発しているアプリで、「署名検証のライブラリは何を使っているか?」「`alg` のチェックは入っているか?」を再確認してみてください。もし不安があれば、いつでもドキュメントを読み返しましょう。それが、プロへの第一歩です。
皆さんの作るシステムが、誰にとっても安全な場所でありますように。応援しています!

コメント