JWTまとめとnode.js で JWTを検証(verify)する auth0 / node-jsonwebtoken

● JWTをざっくり説明すると以下の通り

・ヘッダ、ペイロード(データ本体)、署名をドットでつないで Base64 エンコードしたもの。

・中身は Base64エンコードしただけなので、ペイロードの中身は誰でも見ることができる。

・電子署名を使用して、ペイロードが改ざんされていないか?、正規の発行元以外から勝手に発行されたものかどうか?などを検証することができる。

・よく「JWT脆弱性」というワードが出てくるがJWT自体に脆弱性はない。
(正しくは「JWTを使用した認証システムに脆弱性が存在することがある」)

● node.js で jwt を扱うパッケージ auth0 / node-jsonwebtoken

https://github.com/auth0/node-jsonwebtoken

● node.js 以外の他の言語で jwt を扱うパッケージ

https://jwt.io/libraries

● JWTの署名のアルゴリズム

alg Parameter Value Digital Signature or MAC Algorithm
HS256 HMAC using SHA-256 hash algorithm
HS384 HMAC using SHA-384 hash algorithm
HS512 HMAC using SHA-512 hash algorithm
RS256 RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
RS384 RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
RS512 RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
PS256 RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS384 RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS512 RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 OR >=8.0.0)
ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
none No digital signature or MAC value included

引用: https://github.com/auth0/node-jsonwebtoken

jwtのアルゴリズムを知る方法

https://jwt.io/ にトークンを入力するとヘッダに

{
  "alg": "RS256",
  "kid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "typ": "JWT"
}

と表示されます。("alg": "RS256" なので RS256)

HS256とRS256の違いは何ですか?

HS256

HS256( _ hmac _ with SHA-256)は、 対称アルゴリズム で、2者間で共有される唯一の(秘密)キーを持ちます。署名の生成と検証の両方に同じキーが使用されるため、キーが危険にさらされないように注意する必要があります。

RS256

RS256( SHA-256 を持つRSA署名)は 非対称アルゴリズム で、公開鍵と秘密鍵のペアを使用します。アイデンティティプロバイダには署名の生成に使用される秘密(秘密)鍵があります。 JWTの利用者は、署名を検証するための公開鍵を入手します。秘密鍵ではなく公開鍵を保護する必要がないため、ほとんどのアイデンティティプロバイダは、(通常メタデータURLを通じて)消費者が簡単に入手して使用できるようにしています。

HS256は危険です。RS256を使いましょう。
Google Firebase Auth , Amazon AWS Cognito の accessToken は RS256 です。

● サーバーサイドでクライアントから受け取った「jwtの検証」

サーバーサイドではクライアントから受け取ったjwtの以下の点を検証します

・jwtのペイロード(データ本体)に「改ざんがあるかどうか?」の検証
・想定している発行元以外から「勝手に発行されたjwtかどうか?」の検証
・jwtの有効期限が「有効期限内であるかどうか?(jwtが有効か?)」の検証

● node-jsonwebtoken を使った検証(verify)のサンプル

Google Firebase の Authentication で受け取ったトークンを検証します。
検証に使用する公開鍵はGoogleのサーバから取得します。

npm install  jsonwebtoken axios
const axios = require("axios").default;
const jwt = require("jsonwebtoken");

/**
 * 
 * @param {string} accessToken 
 */
const verifyJWT = async (accessToken) => {
  const kid = getKidFromJwtHeader(accessToken);
  const publicKey = await getPublicKeyFromFirebase('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', kid);

  jwt.verify(accessToken, publicKey, function (err, decoded) {
    if (err) {
      console.log('● verifyでエラーが発生');
      console.log(err);
    }
    else if (decoded) {
      console.log('● verify OK');
      console.log(decoded);
    }
    else {
      console.log('● JWTのデコードができませんでした');
    }
  });
}

/**
 * jwtからヘッダの中のkidを取得
 * @param {string} token 
 * @returns {string} key 
 */
const getKidFromJwtHeader = (token) => {
  const jwtHeader = getJwtHeader(token)
  const obj = JSON.parse(jwtHeader)
  return obj.kid
}

/**
 * jwtからヘッダを取得
 * @param {string} token 
 * @returns {string} key 
 */
const getJwtHeader = (token) => {
  const tokenHeader = token.split('.')[0]
  const jwtHeader = Buffer.from(tokenHeader, 'base64').toString()
  return jwtHeader
}

/**
 * Firebaseのjwt検証用公開鍵を取得する
 * @param {string} url 
 * @param {string} kid 
 * @returns {string} key 
 */
const getPublicKeyFromFirebase = async (url, kid) => {
  const res = await axios.get(url)
  const publicKeys = res.data
  const key = publicKeys[kid]
  return key
}


verifyJWT("<検証したいトークン>");

● クライアント(WEBブラウザ)で受け取ったJWTの保存先

○「HTTP OnlyでSecureなCookieに保存する」
×「LocalStorageに保存する」 ( local storageはあらゆるJavaScriptコードから自由にアクセスできてしまいます )

引用: https://bit.ly/3fMiqVy

● 他にjwtを取り扱う上で気をつけること

・共通鍵のアルゴリズムは選択しない
・鍵の長さは256bit以上とする
・検証をせずにデコードだけすると、改ざんまたは勝手に発行されたトークンを見ている可能性がある
(なので alg=none を使用するのはありえない)
・認証NGとなるjwtでも、データの中身は誰でも見れるので機密情報はjwtには含めない
No.2222
10/07 14:17

edit