// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A token issued and checked twice over — once with a shared secret, once with
/// a key pair read out of a JWK.
///
/// moon run src/examples/tour
#coverage.skip
fn main raise {
let claims : Map[String, Json] = Map([])
claims["sub"] = Json::string("heke1228")
claims["iss"] = Json::string("moonbitstack")
claims["aud"] = Json::string("moonapi")
claims["exp"] = Json::number(4102444800.0, repr="4102444800")
claims["scope"] = Json::string("read write")
let now = 1758412800L
// HS256: one shared secret signs and verifies.
let secret = b"a shared secret nobody else has"
let hs = @jwt.mac(fn() {
@hmac.Hmac::new(secret[:], fn() { @sha2.Hasher::new() })
})
let token = @jwt.sign(claims, HS256, hs)
println("HS256 token -> \{token}")
let got = @jwt.verify(
token[:],
HS256,
hs,
now=At(now),
issuer="moonbitstack",
audience=["moonapi"],
)
println("HS256 subject -> \{@json.dumps(got.get("sub").unwrap())}")
// The attack this refuses: the same bytes, read as another algorithm.
try @jwt.verify(token[:], HS384, hs, now=At(now)) |> ignore catch {
Algorithm(want~, got~) =>
println("confusion -> refused: \{got} is not \{want}")
_ => println("confusion -> refused")
} noraise {
_ => println("confusion -> accepted, which would be the bug")
}
// Reading a token without asking whether it has expired: a debugging tool, an
// audit of tokens already collected, a migration that must read last year's.
let stale = @jwt.sign(
{
let old : Map[String, Json] = Map([])
old["sub"] = Json::string("heke1228")
old["exp"] = Json::number(1000.0, repr="1000")
old
},
HS256,
hs,
)
try @jwt.verify(stale[:], HS256, hs, now=At(now)) |> ignore catch {
Expired(at~) => println("expired -> refused at \{at}")
_ => println("expired -> refused")
} noraise {
_ => println("expired -> accepted, which would be the bug")
}
let forced = @jwt.verify(stale[:], HS256, hs, now=Ignored)
println("ignored time -> \{@json.dumps(forced.get("sub").unwrap())}")
// EdDSA, with the key read from a JWK as a server would read it.
let key = @jwk.Jwk::parse(
"{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"kid\":\"2026-09\",\"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\",\"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}",
)
match key.material() {
Okp(x~, secret=Some(seed), ..) => {
let sk = @ed25519.PrivateKey::new(seed[:])
let named : Map[String, Json] = Map([])
named["kid"] = Json::string(key.id().unwrap())
let signed = @jwt.sign(claims, EdDSA, sk, extra=named)
println("EdDSA token -> \{signed}")
// A verifier reads `kid` first, then fetches the key, then verifies.
println(
"kid -> \{@json.dumps(@jwt.header(signed[:]).get("kid").unwrap())}",
)
let pk = @ed25519.PublicKey::new(x[:])
@jwt.verify(signed[:], EdDSA, pk, now=At(now)) |> ignore
println("EdDSA ok -> true")
// What a key set may be published as: the public half only.
println("published -> \{@json.dumps(key.json())}")
}
_ => println("the JWK did not carry an Ed25519 key pair")
}
}