blog.ojisan.io

thumbnail

Fastly Compute@Edge + Rust で JWT を decode する

たかが JWT と思っていたらそれなりに苦戦したのでメモ

準備

encode した JWT は適当に NodeJS などで用意しておく。

const jwt = require("jsonwebtoken");
const fs = require("fs");
const PRIVATE_KEY = fs.readFileSync("secret.key");
const payload = {
  name: "太郎",
  age: 3,
};
const expirationSeconds = 60 * 5;
const token = jwt.sign(payload, PRIVATE_KEY, {
  expiresIn: expirationSeconds,
  algorithm: "RS256",
});

console.log("token ->", token);

署名に使う鍵は

openssl genrsa -out secret.key 2048
openssl rsa -in secret.key -pubout -out public.key

で作っておく。

Rust で decode

ライブラリ選定

ここでは jwt_simple を使う。 おそらくだがこのライブラリくらいしか使えない気がする。 と言っても、jsonwebtoken という人気のあるライブラリがあるにはあるものの、この crate の依存 crate が WASI 向けのコンパイルに対応していないようである。

error[E0432]: unresolved import `super::sysrand_chunk`
   --> /Users/ojisan/.cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/src/rand.rs:306:16
    |
306 |     use super::sysrand_chunk::chunk;
    |                ^^^^^^^^^^^^^ could not find `sysrand_chunk` in `super`

FYI: https://github.com/briansmith/ring/issues/1043

この ring というライブラリは NodeJS でいう crypto モジュール並のもので、様々なものがこれに依存しているため、C@E で使えるものは大きく限られてしまうのである。

ところで公式には C@E 上で JWT を処理する例やブログがある。

FYI: https://developer.fastly.com/solutions/examples/json-web-tokens

FYI: https://github.com/fastly/compute-rust-auth

FYI: https://www.fastly.com/jp/blog/simplifying-authentication-with-oauth-at-the-edge

なので、技術的にはできるはずで、それらで使われているライブラリが jwt_simple である。

decode

公式の例を見ると decode は

pub fn validate_token_rs256<CustomClaims: Serialize + DeserializeOwned>(
    token_string: &str,
) -> Result<JWTClaims<CustomClaims>, Error> {
    let public_key = RS256PublicKey::from_pem(include_str!("public.key"))?;
    public_key.verify_token::<CustomClaims>(token_string, None)
}

とすれば良さそうだ。ここでは鍵の中身は include_str!("public.key") で取り出している。

ところどころで出てきている CustomClaims は JWT に埋め込んだ payload である。それは JWS で定められた構造と違って独自の構造を持つのでユーザーが宣言しなければいけない。それは構造体で宣言される。今回の場合だと、

#[derive(Serialize, Deserialize, Debug)]
struct CustomClaim {
    name: String,
    age: u8,
}

として定義される。

verify_token 関数はトレイト境界として Serialize + DeserializeOwned 要求するので、serde を入れて derive しておく必要がある。

公式の例だと CustomClaim の代わりに NoCustomClaims を指定したりもするが、これは空の構造体であり、その結果 decode した値から body を取れなくなるので使わないようにしよう。