はじめに
Webアプリに必要不可欠な認可処理。その中でもtokenを用いて認可処理を行うことはよくある。
本記事ではJWTを用いたtoken認可処理を行うにあたり、JWTがどのようにエンコード、デコードされているか実際にGASで関数の作成を行い解説する。
JWTのしくみ
JWTの定義は、公式で「JSON Web Token (JWT)は、関係者間で情報を JSON オブジェクトとして安全に送信するためのコンパクトで自己完結型の方法を定義するオープン標準です。」と言及されている。
JWTは以下3つの要素をBASE64に変換した文字列で構成されており、ハッシュ化あるいは公開鍵を利用することでトークンの正当性を検証できるという特徴がある。
- ヘッダ
- ペイロード
- 署名
これら3つの要素をドット(.)
で区切り構成される。すなわち、通常以下のような構成となる。
$HERADER.$PAYLOAD.$SIGN
1. ヘッダ
ヘッダにはトークンのタイプ(JWT)と、署名に利用するアルゴリズムを記載する。
{
"alg": "HS256",
"type": "JWT"
}
GASのコードでは以下のようになる。
const header = Utilities.base64Encode(
JSON.stringify({ "alg":"HS256", "typ":"JWT" }),
Utilities.Charset.UTF_8,
)
ヘッダのJSONを作成、BASE64でエンコードしている。署名アルゴリズムはHS256の他に、RS256などの公開鍵認証も利用できる。
2. ペイロード
ペイロードにはトークンに含める情報を記載する。ペイロードに記載される情報はクレームと呼ばれ、以下の3種類がある。
- 登録済みクレーム: 事前に定義されたクレームで、
iss
、exp
、sub
、aud
などがある - パブリッククレーム: IANA JSON Web Token Registryで定義されたクレーム
- プライベートクレーム: 情報を共有するために定義されたカスタムクレーム
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
GASのコードでは以下のようになる。
const payload = Utilities.base64Encode(
JSON.stringify({ "sub": "1234567890", "name": "John Doe", "admin": true }),
Utilities.Charset.UTF_8
)
PayloadのJSONを作成、BASE64でエンコードしている。
3. 署名
署名はヘッダとペイロードを結合した情報から、ヘッダに記載したアルゴリズムでハッシュ化あるいは暗号化を行い作成する。
トークンの検証は、受け取ったJWTのヘッダとペイロードから再度署名を作成し、入力されたJWTの署名と一致するかで行う。
GASのコードでは以下のようになる。
const signature = Utilities.base64Encode(
Utilities.computeHmacSha256Signature(
`${header}.${payload}`,
SECRET_KEY
)
)
SECRET_KEY
はハッシュ化に利用する任意のシークレットキーである。ヘッダとペイロードを結合しハッシュ化、その後Base64でエンコードしている。
エンコード
上記を踏まえ、GASでJWTのエンコードを行う関数は以下のようになる。
function encode(payload, key) {
const header = Utilities.base64Encode(
JSON.stringify({ "alg":"HS256", "typ":"JWT" }),
Utilities.Charset.UTF_8
)
const encodeText = header + "." + Utilities.base64Encode(
payload,
Utilities.Charset.UTF_8
)
const signature = Utilities.computeHmacSha256Signature(encodeText, key)
return encodeText + "." + Utilities.base64Encode(signature)
}
関数の引数は各々ペイロードのJSONと、ハッシュ化に利用するシークレットキーである。ヘッダとペイロードを作成し、その後署名を作成。最後に結合して関数の戻り値にする。
デコード
反対に、GASでデコードを行う関数は以下のようになる。
function decode(token, key) {
const [header, payload, signature] = token.split(".")
const decodedHeader = JSON.parse(Utilities.newBlob(
Utilities.base64Decode(
header,
Utilities.Charset.UTF_8
)
).getDataAsString())
if (decodedHeader.alg !== "HS256") return { payload: null, success: false }
const validSignature = Utilities.base64Encode(
Utilities.computeHmacSha256Signature(`${header}.${payload}`, key)
)
if (signature !== validSignature) return { payload: null, success: false }
const decodedPayload = JSON.parse(Utilities.newBlob(
Utilities.base64Decode(
payload,
Utilities.Charset.UTF_8
)
).getDataAsString())
if (decodedPayload.exp < Date.now() / 1000)
return { payload: null, success: false }
return { payload: decodedPayload, success: true }
}
関数の引数は各々JWTとハッシュ化に利用するシークレットキーである。
入力されたJWTをヘッダ、ペイロード、署名の3つに分割する。ヘッダから署名のアルゴリズムを取得し、入力されたJWTのヘッダとペイロードから再度署名を作成する。作成した署名が入力されたJWTの署名と一致しない場合、内容が書き換えられているため検証失敗とする。続けて、ペイロードの期限を検証し、過ぎていない場合はペイロードの内容を関数の戻り値にする。
まとめ
本記事では、GASを利用してJWTを発行・検証する関数の作成を行うことでJWTのアルゴリズムを解説した。詳しくはJWTの公式サイトを確認してほしい。
では、良いエンジニアライフを。