概要
OpenID Connectで必須なJWT
の検証方法です。
以前書いた「Node.jsでOpenID Connect認証」を前提としています。
検証方法は主に2つあります。
- Googleの検証用エンドポイントを使う
- 公開鍵を使い自分で検証する
公開鍵の方はさらに2つやり方があるので、順に紹介します。
環境
- Nodejs 0.10.22
事前準備(ID Tokenを取得)
https://github.com/jaredhanson/passport-openidconnect/blob/master/lib/strategy.js#L104
ここで作者がconsole.log
を出しているので、ターミナルで出力を見るとJWT形式のID Token
が分かります。
私の場合以下の様なID Token
が取得出来ました。※個人情報のため値をいじってます
{ access_token: 'ya29.BwEd7bBEGsXCsuuSvvjYZCqgmXzLy1ho69P0g_kVjR_a_9e6RMlsS4Wl-o17rvA3SfsEkpvnVnXpg', token_type: 'Bearer', expires_in: 3600, id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjEZjgyOTViMjM3MGRkODhkZTRjMjJhZWQ4NWNhYmY1MmQyNzU3ZjUifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyOTUyOTA4OTYzOTU1NzQwMjc0IiwiYXpwIjoiNjk4MTAwMDA3MjQyLTRqcDQxZzltYzUwdIwa2syMmwxMTNhbnBnYWduMGNpLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlB5QUFtYzBWN0lzMlZKamdmQ2lVYXciLCJhdWQiOiI2OTgxMDAwMDcyNDItNGpwNDFnOW1jNTB2cjBrazIybDExM2FucGdhZ24Y2kuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0MjIyODE3MjUsImV4cCI6MTQyMjI4NTYyNX0.sG65mFYE-zuGlAYOh0e1E5qmhKGhR_-wmJsMEYNaBtGqBIi-czbYVJVZRPqraKxUST1-SCdWb3VcQvhs4gIXA2x5w8bP7yOul-_aXx8kcEJp2NnzaTgdBQ7k0oE3vAaKE_8Kg3JxGc0_0apq9aJ9cyGHwrHbazs-bgGqqCKqmQ' }
検証方法①:Googleの検証用エンドポイントを利用する
https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=
のクエリパラメータに先ほどのID Token
を付けてリクエストを投げると結果を返してくれます。
間違ったID Token
だと以下の様な結果になります。
{ "error": "invalid_token", "error_description": "Invalid Value" }
正しいID Token
だと以下の様なレスポンスが返ります。
{ "issuer": "accounts.google.com", "issued_to": "my_service_client_id", "audience": "my_service_client_id", "user_id": "user_unique_id", "expires_in": 3561, "issued_at": 1422281725 }
確認する項目は以下です。
issuer
がgoogleであることissued_to
、audience
は自分のサービスのclientId
と一致していること
これが正しければ、このID Token
は自分のサービス用に発行されてたものであると確証できます。
検証方法②:IdP(Google)の提供している公開鍵を使って自分で検証する
以下のライブラリを使って検証します。
$ npm install jsonwebtoken
このライブラリで検証するために必要なのは以下の2つです。
- IDToken
- IdPの公開鍵
後者の公開鍵ですが、OpenIDConnectを提供しているIdPは主に以下の2つの方法で公開鍵を提供しています。ちなみにGoogleの場合これら公開鍵は1日に1回変わるらしいです。
- JWK(JSON Web Key)
- X.509証明書
今回はそれぞれのやり方を紹介します。
公開鍵①:JWK(JSON Web Key)を使う
JWKの取得
Googleは以下のエンドポイントでJWK形式の公開鍵を公開しています。
https://www.googleapis.com/oauth2/v3/certs
アクセスすると以下の様なレスポンスが返ります。
{ "keys": [ { "kty": "RSA", "alg": "RS256", "use": "sig", "kid": "16f8295b2370dd88de4c22aed85cabf52d2757f5", "n": "tL3TtFEjtVIlFrMsyRncvS6ZLDRr6PejeQv7hx1k-oX0599OTYA4FQE8YYX4z95_NaQXx833DPay7KVzw751kHJz9eiSYyZmYFMM786E-PspFvdJMhU2ZCLgxLUXZ_Gq7ORgxHkJHcBWR8HstjI3zpWAOhfqg8YvSnMeOStQ1Ns=", "e": "AQAB" }, { "kty": "RSA", "alg": "RS256", "use": "sig", "kid": "8472c6590b1778fe529c1bd3a8f181cc2af4b200", "n": "rIVm3h1WGbvKjmvzrpwPFeyAWIeP3W87z-C9k0YarePIF0Y77KgaMB83cVv5Hp85Che-Z_nb_y0kBhrOha4_q_6gFEOhyz8PUZSzdY2zkhX8Dci-vic9HulL5cFWjDGPXwekHLm_EmXkPkKu7-6nbkxmwcVQMGX2lEeawCqqNmk=", "e": "AQAB" } ] }
この中のn
とe
を使います。それぞれは
- n: modulus
- e: exponent
として、これらを用いてRSA署名検証に使われる公開鍵を取り出すことができます。
JWKから公開鍵を生成する
こちらもライブラリを作っている方がいらっしゃるので利用させて頂きます。
まず試しにどうやって公開鍵を生成するか使ってみます。
$ npm install rsa-pem-from-mod-exp
publicKey.js
として以下のファイルを作ります。
//getPem = function(modulus_base); var getPem = require('rsa-pem-from-mod-exp'); //modulus should be a base64/base64Url string var modulus = "tL3TtFEjtVIlFrMsyRncvS6ZLDRr6PejeQv7hx1k-oX0599OTYA4FQE8YYX4z95_NaQXx833DPay7KVzw751kHJz9eiSYyZmYFMM786E-PspFvdJMhU2ZCLgxLUXZ_Gq7ORgxHkJHcBWR8HstjI3zpWAOhfqg8YvSnMeOStQ1Ns="; //exponent should be base64/base64url var exponent = "AQAB"; var pem = getPem(modulus, exponent); console.log(pem);
こんな感じにすると以下のように出力されます。
-----BEGIN RSA PUBLIC KEY----- MIGJAoGBALS907RRI7VSJRazLMkZ3L0umSw0a+j3o3kL+4cdZPqF9OffTk2AOBUB PGGF+M/efzWkF8fN9wz2suylc8O+dZByc/XokmMmZmBTDO/OhPj7KRb3STIVNmQi 4MS1F2fxquzkYMR5CR3AVkfB7LYyN86VgDoX6oPGL0pzHjkrUNTbAgMBAAE= -----END RSA PUBLIC KEY-----
JWKでID Tokenを検証する
公開鍵の作り方が分かったので、このやり方で最初に挙げたJWT検証ライブラリjsonwebtoken
で利用します。
test.js
として以下のファイルを作成します。
var jwt = require('jsonwebtoken'); var getPem = require('rsa-pem-from-mod-exp'); var token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjEZjgyOTViMjM3MGRkODhkZTRjMjJhZWQ4NWNhYmY1MmQyNzU3ZjUifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyOTUyOTA4OTYzOTU1NzQwMjc0IiwiYXpwIjoiNjk4MTAwMDA3MjQyLTRqcDQxZzltYzUwdIwa2syMmwxMTNhbnBnYWduMGNpLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlB5QUFtYzBWN0lzMlZKamdmQ2lVYXciLCJhdWQiOiI2OTgxMDAwMDcyNDItNGpwNDFnOW1jNTB2cjBrazIybDExM2FucGdhZ24Y2kuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0MjIyODE3MjUsImV4cCI6MTQyMjI4NTYyNX0.sG65mFYE-zuGlAYOh0e1E5qmhKGhR_-wmJsMEYNaBtGqBIi-czbYVJVZRPqraKxUST1-SCdWb3VcQvhs4gIXA2x5w8bP7yOul-_aXx8kcEJp2NnzaTgdBQ7k0oE3vAaKE_8Kg3JxGc0_0apq9aJ9cyGHwrHbazs-bgGqqCKqmQ'; var modulus = "tL3TtFEjtVIlFrMsyRncvS6ZLDRr6PejeQv7hx1k-oX0599OTYA4FQE8YYX4z95_NaQXx833DPay7KVzw751kHJz9eiSYyZmYFMM786E-PspFvdJMhU2ZCLgxLUXZ_Gq7ORgxHkJHcBWR8HstjI3zpWAOhfqg8YvSnMeOStQ1Ns="; var exponent = "AQAB"; var cert = getPem(modulus, exponent); jwt.verify(token, cert, function(err, decoded) { console.log('error: ', err) console.log('result: ', decoded) });
実行します。
$ node test.js
すると以下の様な結果となります。
error: null result: { "iss": "accounts.google.com", "sub": "user_unique_id", "azp": "my_service_client_id", "at_hash": "PyAAmc0V7Is2VJjgfCiUaw", "aud": "my_service_client_id", "iat": 1422281725, "exp": 1422285625 }
Googleに検証してもらった時と同じく以下の項目をチェックします。
iss
がgoogleであることaud
は自分のサービスのclientId
と一致していること
大丈夫そうでしたら正しいID Token
ということになります。
公開鍵①:X.509証明書を使う
証明書の取得
GoogleはX.509形式の証明書も公開しています。
https://www.googleapis.com/oauth2/v1/certs
\n
の改行記号を普通の改行にして、public.pem
として保存します。
-----BEGIN CERTIFICATE----- MIICITCCAYqgAwIBAgIIcAilVKRkTx4wDQYJKoZIhvcNAQEFBQAwNjE0MDIGA1UE AxMrZmVkZXJhdGVkLXNpZ25vbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTAe Fw0xNTAxMjUyMDU4MzRaFw0xNTAxMjcwOTU4MzRaMDYxNDAyBgNVBAMTK2ZlZGVy YXRlZC1zaWdub24uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wgZ8wDQYJKoZI hvcNAQEBBQADgY0AMIGJAoGBALS907RRI7VSJRazLMkZ3L0umSw0a+j3o3kL+4cd ZPqF9OffTk2AOBUBPGGF+M/efzWkF8fN9wz2suylc8O+dZByc/XokmMmZmBTDO/O hPj7KRb3STIVNmQi4MS1F2fxquzkYMR5CR3AVkfB7LYyN86VgDoX6oPGL0pzHjkr UNTbAgMBAAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1Ud JQEB/wQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAH+l4BlDBOoxAv6/ l8CI81tbEgzlQGZ/aQKDVhUC21tkUiCS3M39ZXOrK54Aav/nDPArAu7KMCvGyNrW tIQvrf5Mi14C9Y9mk2imFPtxGjOF6haxfCGkubnFY05Two/dkUG7Qm8bzJQy5j23 /0QCIx4dSMiv0ON6GFlMAKbPglM5 -----END CERTIFICATE-----
証明書でID Tokenを検証する
test.js
というファイルを作成します。
var jwt = require('jsonwebtoken'); var fs = require('fs'); var token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjEZjgyOTViMjM3MGRkODhkZTRjMjJhZWQ4NWNhYmY1MmQyNzU3ZjUifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyOTUyOTA4OTYzOTU1NzQwMjc0IiwiYXpwIjoiNjk4MTAwMDA3MjQyLTRqcDQxZzltYzUwdIwa2syMmwxMTNhbnBnYWduMGNpLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlB5QUFtYzBWN0lzMlZKamdmQ2lVYXciLCJhdWQiOiI2OTgxMDAwMDcyNDItNGpwNDFnOW1jNTB2cjBrazIybDExM2FucGdhZ24Y2kuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0MjIyODE3MjUsImV4cCI6MTQyMjI4NTYyNX0.sG65mFYE-zuGlAYOh0e1E5qmhKGhR_-wmJsMEYNaBtGqBIi-czbYVJVZRPqraKxUST1-SCdWb3VcQvhs4gIXA2x5w8bP7yOul-_aXx8kcEJp2NnzaTgdBQ7k0oE3vAaKE_8Kg3JxGc0_0apq9aJ9cyGHwrHbazs-bgGqqCKqmQ'; var cert = fs.readFileSync('public.pem'); jwt.verify(token, cert, function(err, decoded) { console.log('error: ', err) console.log('result: ', decoded) });
こうすると以下の様な結果になります。
error: null result: { "iss": "accounts.google.com", "sub": "user_unique_id", "azp": "my_service_client_id", "at_hash": "PyAAmc0V7Is2VJjgfCiUaw", "aud": "my_service_client_id", "iat": 1422281725, "exp": 1422285625 }
JWKの時と同じ結果になりましたね。
以上です。お疲れ様でした。