O hirunewani blog

Q. JWTから渡されたBase64文字列をwindow.atob()に渡すとエラーが出る場合がある

Created at

JWTをクライアントでデコードする際にエラーが出る場合があると相談を受けた際の調査結果をまとめた。

JWTをクライアントでデコードする際に、エラーが出る場合があると相談を受けた。

window.btoa()などにはUnicode 問題があることが知られており、元の文字列に日本語が含まれているケースでのみ発生していたため、それが原因であると考えたが、文字列によっては日本語でもエラーが出ないケースがあったため、 追加で調査を行った。

Base64の変換方法について

Base64はバイナリーからテキストへの符号化を行う手法であり、転送中にバイナリデータが変換されていないことを保証できる。次のような変換を行う。

const uint8arr = Array.from(new TextEncoder().encode("ABCDEFG"));
// (7) [65, 66, 67, 68, 69, 70, 71]
const b8 = uint8arr.map(v => v.toString(2).padStart(8, "0"));
// (7) ['01000001', '01000010', '01000011', '01000100', '01000101', '01000110', '01000111']
const b6 = b8
  .join("")
  .padEnd(Math.ceil(b8.length / 6) * 6, "0")
  .match(/.{6}/g);
// (10) ['010000', '010100', '001001', '000011', '010001', '000100', '010101', '000110', '010001', '110000']

// 変換表に従って変換する
const t = convert(b6).map(v => v.padEnd(4, "="));
// ["QUJD", "REVG"、"Rw=="]
t.join("");
// QUJDREVGRw==

6ビットのビット列は、それぞれ次のよいに変換表で定義された英数字(A-Za-z0-9)+2文字に変換される。

000000A
000001B
000010C

Base64には複数種類あり、追加2文字の種類や=で詰めるかなど異なる。

JWTとwindow.atob()が利用するBase64は異なる

window.atob()やwindow.btoa()JWT
仕様https://datatracker.ietf.org/doc/html/rfc4648#section-4: base64 (standard)https://datatracker.ietf.org/doc/html/rfc4648#section-5: base64url (URL- and filename-safe standard)
111110+-
111111/_

このためJWTとwindow.atobではBase64の差分である _- がある場合、エラーが出てしまう。

atob("LCJz...dWIiOiLjgZXjgY_jgonjg5H...jg7");
// Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

この問題はLatin文字かどうかに限らず発生するように思えるかもしれないが、Latin文字で1が5個以上連続することは恐らくほぼないため、日本語を利用しているケースでエラーが発生した。

日本語でエラーが出ていないケースでは、追加2文字が含まれていないだけで、atobの実行結果は文字化けしていた。