こんにちは!セキュリティの現場を渡り歩いてきたホワイトハッカーの私ですが、今日は新人のIT担当者や、これからWebアプリ開発に挑む皆さんに、絶対におさえておいてほしい「認証の要」についてお話ししますね。
モダンなWebサービスやスマホアプリでは、ログインの仕組みとして OpenID Connect (OIDC) という技術が当たり前のように使われています。このOIDCの主役とも言えるのが、ユーザーの身分証明書である「IDトークン(JWT)」です。
今回は、このIDトークンを安全に扱うための「署名検証」と「クレーム(iss/aud)検証」について、身近な防犯の例えを交えながら、一歩ずつ優しく紐解いていきましょう!
—
1. 家の鍵と合鍵で例える「IDトークン」の仕組み
まずは、IDトークンがどんなものか、私たちの身近な「家と鍵」に例えて考えてみましょう。
あなたがマンションの管理人(認証局:GoogleやAuth0など)だとします。入居者(ユーザー)があなたに身分証を見せて本人確認を済ませた後、あなたは入居者に対して「この人は間違いなく部屋の住人です」と書かれた証明書(IDトークン)を発行します。
入居者は、その証明書を自分の部屋のスマートロック(あなたのWebアプリケーション)に見せることで、部屋の中に入ることができます。
ここで大きな疑問が湧きますよね。
「もし、悪意ある泥棒が、自分で紙に『私は住人です』と勝手に書いて持ってきたらどうなるでしょうか?」
はい、簡単に部屋に入られてしまいますよね!これが世に言う「なりすまし攻撃」です。
この偽造を防ぐために使われるのが、管理人だけが押すことのできる「絶対に真似できない特殊なハンコ(電子署名)」なんです。
—
2. JWT(JSON Web Token)の構造と「署名」の正体
IDトークンとして一般的に使われるのが JWT(JSON Web Token) というフォーマットです。中身をのぞくと、ドット(.)で区切られた3つのパートに分かれています。
1. ヘッダー (Header): 「どんなハンコ(アルゴリズム)を使ったか」の情報
2. ペイロード (Payload): 「誰のどんな情報か(クレーム)」本体
3. 署名 (Signature): 改ざんされていないことを証明するハンコ
ここでセキュリティ担当者が真っ青になる、攻撃者がよく狙う「最初の盲点」があります。それが「署名アルゴリズムの強制指定(脆弱性)」です。
攻撃者の手口:ハンコを勝手に「なし」にする!?
古いライブラリや実装ミスがあるアプリでは、ヘッダーに書かれた「このアルゴリズムを使って署名を確認してね」という指示を、そのままうのみにしてしまうバグが存在します。
攻撃者は、次のような悪巧みをします。
1. ペイロードの「私は管理者だ」というデータを自分で書き換える。
2. ヘッダーのアルゴリズムを none(署名なし)に書き換える。
3. アプリ側に送りつける。
もしアプリ側が「おっ、ヘッダーに none って書いてあるから、今回はハンコの確認はしなくていいんだな!」と素直に受け入れてしまったら……。はい、ゲームオーバーです。泥棒の侵入成功です。
だからこそ、開発者である私たちが最初にやるべき鉄則は、「使うアルゴリズムをコード側でガチガチに固定(ホワイトリスト化)すること」なのです。
—
3. 署名検証とクレーム(iss / aud)の二重チェック
無事にハンコが本物だと確認できたら、次にやるべきなのが「中身の文字(クレーム)のチェック」です。ここを手を抜くと、また別の痛い目をみます。
特に重要なのが、次の2つのクレームです。
- iss (Issuer / 発行者): 「この証明書を出したのは、本当に信用できる管理人(例:
https://accounts.google.com)か?」 - aud (Audience / オーディエンス): 「この証明書は、私の部屋(アプリ)宛てに出されたものか?」
身近な例え:コンサートのチケットと身分証
例えば、あなたが人気アーティストのライブ会場のスタッフだとしましょう。
チケット(IDトークン)の半券を見たとき、以下の2つを確認しませんか?
1. 「これ、偽造チケットじゃないよね?」 = 署名検証
2. 「このチケット、今日のこの会場(うちのアプリ)の、しかもこの席のチケットだよね?(隣町の劇場のチケットじゃないよね?)」 = aud (オーディエンス) 検証
3. 「そもそも、チケットを発行した公式のぴあやイープラスから直接配られたものだよね?」 = iss (発行者) 検証
もし、隣町の全然関係ない劇場のチケットを持ってこられても、入場させてはいけませんよね。別のアプリ用に発行された本物のIDトークンを、あなたのアプリに送りつけてログインさせようとする「クロスサイト・リクエスト・フォージェリ(CSRF)的アプローチ」や「トークン流用攻撃」を防ぐために、この iss と aud の突合せが絶対に不可欠なのです。
—
4. 【実装例】安全なIDトークン検証のコード
それでは、実務でそのまま参考にできるように、Node.js(jsonwebtoken ライブラリなどを使用するイメージ)をベースにした安全な検証ロジックのサンプルを見てみましょう。
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
// 1. 信頼できる公開鍵の取得先(JWKSエンドポイント)を設定する
const client = jwksClient({
jwksUri: 'https://your-identity-provider.com/.well-known/jwks.json'
});
function getKey(header, callback) {
client.getSigningKey(header.kid, function(err, key) {
if (err) {
return callback(err);
}
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
// 2. IDトークンの検証関数
function verifyIdToken(tokenString, expectedAudience) {
// 検証オプションの定義(ここで安全の網をしっかり張ります)
const options = {
// 【重要】攻撃者が 'none' や脆弱なアルゴリズムを指定するのを防ぐため、RS256に厳格に固定する
algorithms: ['RS256'],
// 【重要】発行者(iss)が期待通りの正確なURLかチェックする
issuer: 'https://your-identity-provider.com',
// 【重要】宛先(aud)が自社アプリのクライアントIDと一致しているかチェックする
audience: expectedAudience,
};
jwt.verify(tokenString, getKey, options, (err, decodedPayload) => {
if (err) {
// 検証失敗:ログにエラーを残し、認証エラーとして弾く
console.error('IDトークンの検証に失敗しました:', err.message);
throw new Error('不正なアクセスの検出またはトークンの有効期限切れです。');
}
// 検証成功!安全にユーザー情報を利用する
console.log('認証成功! ユーザーID:', decodedPayload.sub);
return decodedPayload;
});
}
このコードのポイントは、options オブジェクトの中にあります。
algorithms: ['RS256'] と明示的に指定することで、万が一ヘッダーに怪しいアルゴリズムが書かれていても、ライブラリ側が強制的に弾いてくれるようになっています。
—
さいごに:セキュリティは「疑うこと」から始まる
セキュリティの世界に足を踏み入れたばかりの頃は、覚えることが多くて圧倒されてしまうかもしれません。でも、基本の考え方はとてもシンプルです。
「ネット上のデータは、基本的にすべて嘘(偽造されたもの)かもしれないと疑うこと」
そして、
「信頼できる機関(公開鍵やIssuer)のハンコと、宛先(Audience)を毎回しっかり指さし確認すること」
この2つさえ頭に置いておけば、OIDCのIDトークン周りの主要な攻撃(アルゴリズム改ざんやトークン流用)のほとんどをきれいに防ぐことができます。
一歩ずつ、確実な実装を積み重ねて、セキュアなWebの世界を一緒に作っていきましょう!それではまた次回のセキュリティ解説でお会いしましょう。
コメント