LoginSignup
18
20

More than 5 years have passed since last update.

Node.jsでAWS Cognito User Pools のアクセストークンを検証する

Last updated at Posted at 2017-03-31

AWS のページから引用

  1. ユーザープールの JSON Web トークン (JWT) セットをダウンロードして保存します。それら をhttps://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json で検索で きます。
  2. JWT 形式からトークン文字列をデコードします。
  3. iss クレームを確認します。これは、ユーザープールと一致する必要があります。たとえ ば、us-east-1 リージョンで作成されたユーザープールの iss 値が https://cognito-idp.useast-1.amazonaws.com/{userPoolId} であるとします。
  4. token_use クレームを確認します。
  5. JWT トークンヘッダーから kid を取得すると、ステップ 1 で保存された対応する JSON Web キーが 取得されます。
  6. デコードされた JWT トークンの署名を確認します。
  7. exp クレームを確認し、トークンが期限切れでないことを確認します。 トークン内でクレームを信頼し、要件に満たすものとしてそれを使用できます。

個人的に5と6がわからなかったので、メモ。

まず1でこんなjsonファイルをダウンロードする

jwks.json
{
    "keys": [
        {
            "alg": "RS256",
            "e": "AAAA",
            "kid": "5XeKL5ebJ5VD5RmIaKqYQHZ9+iEtJXzovVSqvYl0kEk=",
            "kty": "RSA",
            "n": "kbOKCIHPzbnTY9BxmedkQroHsp74X-y-I2ye_nRxzT8jBv6z81WTC1iu_JJypVaw1hw6-zqp4QCjrtqvoWACPkNJfiDoTfw0HEoA3nYYKGyiHtQOpD994bNLSS4jHX3YZyYFsFv26Mj0edlWfjExr0dSX0uuSIEBcuuG0wPm3pflZaQKDuyPQ7RDzyeYz8Y25h_Zpdy0DJKIhdmU4V5BrhMk7oi_wD4VdyqdNWLuhSXUXbRf0H38jYVJoigW5JaDJpqwgkWgiseRRoAxdlInoqxXhecjk1Y03OD-4EKCxWE2oyw7YMWLe73UdHAJZI5JwBtCL1IIjiiHQIoH_Ji_Yw",
            "use": "sig"
        },
        {
            "alg": "RS256",
            "e": "AAAA",
            "kid": "e32pkKHzpmt9V89+cQUTDBRA2+8QEXMF2zkz4WkMxgM=",
            "kty": "RSA",
            "n": "-EOIwnvIrAdHHmJT-YYPLeBvveFh4oYQtl2vhTcOuAAplzCXaYKliH82vGEu5HpaUmj0PJUCEj6pJOMhTvjR509IQPStcyiODEby911gKEf2a-XzawPDGfathM-k_m0FUgvCypfHeBNs-hUgDTVPzDk4jgo8cHu2cIiMqxUgeAn5_l71Hh3WtPg5t2A8Kucz0TH6D3B_-MTZ7r4Vx8X2cg_B7MxmNlnRV55_thMcpxCtkShrRqFTnccMiDC3EYvh7-u0g4i1bvydXEx-_lrQW29CfVwK5yU2NauEBh0F1BGULWLxWAytwzDyuRHReupg4A9dfSqSvB5ckLV8qPmvjQ",
            "use": "sig"
        }
    ]
}

※値は適当です。

検証の流れ

  1. User pool のログインでもらったアクセストークンをdecodeして、kidを取得する。
  2. 上でダウンロードしたファイルのkidn(modulus)とe(exponent)を取得する。
  3. 冪剰余(nとk)からpem文字列を作成する。
  4. アクセストークンをpemを使って検証する。

ソースコード的にはこんな感じ。
accessTokengetAccessToken().getJwtToken()で取れた値。
※RSA公開鍵を作るライブラリが見つからなかったので、他のサイトで同じことやってるものをつかってる。

var jwt = require('jsonwebtoken');
var jwktjson = require('./jwkt.json');
var accessToken = 'eyJraWQiOiIyMzJwa0tIenBtdDlWODkrY1FVVERCUkEyKzhRRVhNRjJ6a3o0V2tNeGdNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJjNGM3YWQ1MS01Y2I1LTQ5ZTctODJjZi0wMGQ1YTExMzI4YmYiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb21cL2FwLW5vcnRoZWFzdC0xXzN4d1RGaVZPNSIsImV4cCI6MTQ5MDk0MjY3NCwiaWF0IjoxNDkwOTM5MDc0LCJqdGkiOiJlYzVhMGExYi00MDg2LTRkNmYtODdhMS00NDUzODdkZjZmOGMiLCJjbGllbnRfaWQiOiI1cW5hOGhxZzVhaml1bjljZzNtMXRpNWdrayIsInVzZXJuYW1lIjoiaGF5YXRvLnNoaW1va2F3YUBuZXh0cmVtZXIuY29tIn0.bgRbZdbvMrmpSE-iGz6v9bSbcTafDaPlOq1TBgxW2W1-F70JQv_Nm_-6QOTtIMP9BtF_wK0UR8L8cWEIUbqou3b5f63yNDhn0J23C7kG1xASdJptnJZ1BG1nIjbAIuuySItef3I_0I089r5bYQYXRLOUdXUhXhpjd8v3hhkArHEAbE0kVX-jWpPGtFm-Ar8peS1m_8TSWg0cUEgFSORyZ789uOv82PovrWhMq9KBEmQtKyWwZq3co5X57TdvMgCq_XXVQa8Ox3rGP1-RzMzDzUEtVzdkBjSkqtvy9yYGh9lfMgvMlK7ljirQUjBp4bWEu7m5Afdr_qWuY7_fG8t6EA';
var decoded = jwt.decode(accessToken, { complete: true });
if (!decoded){
  console.error('accessToken is not JSON Web Token.');
  process.exit(1);
}
var kid = decoded.header.kid;
var n; // modulus
var e; // exponent
for (var i = 0; i < jwktjson.keys.length; i++) {
  var row = jwktjson.keys[i];
  if (kid == row['kid']) {
    n = row['n'];
    e = row['e'];
    break;
  }
}

var pem = rsaPublicKeyPem(n, e);
try {
  var verified = jwt.verify(accessToken, pem, { algorithms: ['RS256'] });
  console.log(verified);
} catch (err) {
  console.error(err);
}
// Create public key PEM from Base64 modulus and exponent
// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
function rsaPublicKeyPem(modulus_b64, exponent_b64) {

  function prepadSigned(hexStr) {
    var msb = hexStr[0]
    if (
      (msb >= '8' && msb <= '9') ||
      (msb >= 'a' && msb <= 'f') ||
      (msb >= 'A' && msb <= 'F')) {
      return '00' + hexStr;
    } else {
      return hexStr;
    }
  }

  function toHex(number) {
    var nstr = number.toString(16)
    if (nstr.length % 2 == 0) return nstr
    return '0' + nstr
  }

  // encode ASN.1 DER length field
  // if <=127, short form
  // if >=128, long form
  function encodeLengthHex(n) {
    if (n <= 127) return toHex(n)
    else {
      n_hex = toHex(n)
      length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes
      return toHex(length_of_length_byte) + n_hex
    }
  }

  var modulus = new Buffer(modulus_b64, 'base64');
  var exponent = new Buffer(exponent_b64, 'base64');

  var modulus_hex = modulus.toString('hex')
  var exponent_hex = exponent.toString('hex')

  modulus_hex = prepadSigned(modulus_hex)
  exponent_hex = prepadSigned(exponent_hex)

  var modlen = modulus_hex.length / 2
  var explen = exponent_hex.length / 2

  var encoded_modlen = encodeLengthHex(modlen)
  var encoded_explen = encodeLengthHex(explen)
  var encoded_pubkey = '30' +
    encodeLengthHex(
      modlen +
      explen +
      encoded_modlen.length / 2 +
      encoded_explen.length / 2 + 2
    ) +
    '02' + encoded_modlen + modulus_hex +
    '02' + encoded_explen + exponent_hex;

  var seq2 =
    '30 0d ' +
    '06 09 2a 86 48 86 f7 0d 01 01 01' +
    '05 00 ' +
    '03' + encodeLengthHex(encoded_pubkey.length / 2 + 1) +
    '00' + encoded_pubkey;

  seq2 = seq2.replace(/ /g, '');

  var der_hex = '30' + encodeLengthHex(seq2.length / 2) + seq2;

  der_hex = der_hex.replace(/ /g, '');

  var der = new Buffer(der_hex, 'hex');
  var der_b64 = der.toString('base64');

  var pem = '-----BEGIN PUBLIC KEY-----\n'
    + der_b64.match(/.{1,64}/g).join('\n')
    + '\n-----END PUBLIC KEY-----\n';

  return pem
}

accessTokenは1時間で切れるので注意。
以下のように無視もできるけども。

jwt.verify(accessToken, pem, { algorithms: ['RS256'], ignoreExpiration: true });

おしまい

18
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
20