・ヘッダ、ペイロード(データ本体)、署名をドットでつないで Base64 エンコードしたもの。
・中身は Base64エンコードしただけなので、ペイロードの中身は誰でも見ることができる。
・電子署名を使用して、ペイロードが改ざんされていないか?、正規の発行元以外から勝手に発行されたものかどうか?などを検証することができる。
・よく「JWT脆弱性」というワードが出てくるがJWT自体に脆弱性はない。
(正しくは「JWTを使用した認証システムに脆弱性が存在することがある」)
https://github.com/auth0/node-jsonwebtoken
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
https://jwt.io/ にトークンを入力するとヘッダに
{
"alg": "RS256",
"kid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"typ": "JWT"
}
と表示されます。("alg": "RS256" なので 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が有効か?)」の検証
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("<検証したいトークン>");
○「HTTP OnlyでSecureなCookieに保存する」
×「LocalStorageに保存する」 ( local storageはあらゆるJavaScriptコードから自由にアクセスできてしまいます )
・共通鍵のアルゴリズムは選択しない
・鍵の長さは256bit以上とする
・検証をせずにデコードだけすると、改ざんまたは勝手に発行されたトークンを見ている可能性がある
(なので alg=none を使用するのはありえない)
・認証NGとなるjwtでも、データの中身は誰でも見れるので機密情報はjwtには含めない