moonbit_jwt

A MoonBit library for JSON Web Token (JWT) creation and verification. Pure MoonBit SHA256 + HMAC implementation, no FFI.

jwt
json-web-token
auth
hmac
sha256
moon add 123123213weqw/moonbit_jwt@0.1.0
Download zip
Version
0.1.0
License
MIT
Last updated
24 days ago
Downloads
3
README

#MoonBit JWT

CI MoonBit License

A pure MoonBit library for JSON Web Tokens (JWT).

Create and verify JWT tokens with HMAC-SHA256/384/512. Includes from-scratch SHA-256, SHA-384, SHA-512, and HMAC implementations — no FFI, no external dependencies, runs on all MoonBit targets.

#Quick Start

import {
"123123213weqw/moonbit_jwt" @jwt,
}

fn main {
// Build claims
let claims = @jwt.ClaimsBuilder::new()
|> @jwt.ClaimsBuilder::issuer("my-app")
|> @jwt.ClaimsBuilder::subject("user123")
|> @jwt.ClaimsBuilder::expires_at(2000000000)
|> @jwt.ClaimsBuilder::to_json_string()

// Sign with HS256
let token = @jwt.sign_hs256(@jwt.JwtHeader::hs256(), claims, "secret".to_bytes())

// Validate with full options
let opts = @jwt.ValidationOptions::new(1000000000)
|> @jwt.ValidationOptions::with_issuer("my-app")
|> @jwt.ValidationOptions::with_leeway(30)
match @jwt.validate(token, "secret".to_bytes(), opts) {
Ok(_) => println("Valid!")
Err(_) => println("Invalid!")
}
}

#Modules

ModuleLinesDescription
sha256.mbt170SHA-256 (FIPS 180-4), streaming + one-shot
sha512.mbt241SHA-512 + SHA-384 (truncated SHA-512)
hmac.mbt30HMAC-SHA256 (RFC 2104)
hmac_full.mbt61HMAC-SHA384 + HMAC-SHA512
base64url.mbt128Base64 URL-safe encode/decode (RFC 4648)
jwt.mbt143JWT types, sign HS256, verify, decode_claims
jwt_full.mbt118HS384/HS512 sign + verify_with_alg
claims.mbt187ClaimsBuilder for structured claim construction
validator.mbt191Full validation (exp/nbf/iss/aud/sub + leeway)
decoder.mbt144Full token decode with header/claims extraction
Total~1900

#Algorithms

AlgorithmHashStatus
HS256HMAC-SHA256
HS384HMAC-SHA384
HS512HMAC-SHA512

#Test Vectors Verified

  • SHA256("") = e3b0c442...
  • SHA256("abc") = ba7816bf...
  • SHA512("abc") = ddaf35a1...
  • SHA384("abc") = cb00753f...
  • HMAC-SHA256("key", "The quick brown fox...") = f7bc83f4...
  • JWT sign → verify roundtrip (HS256/384/512) ✅
  • Token expiration / not-before / issuer / audience validation ✅
  • Tampered token rejection ✅

36 tests, all passing on wasm/wasm-gc/js/native.

#Development

moon fmt --check moon check --deny-warn moon test --deny-warn moon test --target all moon info

#License

MIT

#
Algorithm

pub(all) enum Algorithm {
HS256
HS384
HS512
} derive(Eq,
Debug
)

JWT signing algorithm.

#
Algorithm::to_str

fn Algorithm::to_str(self : Algorithm) -> String

Convert algorithm to its JOSE name string.

#
ClaimsBuilder

pub(all) struct ClaimsBuilder {
claims : Map[String, Json]
}

A builder for constructing JWT claims as a JSON object.

Standard claims (iss, sub, aud, exp, nbf, iat, jti) have dedicated methods; custom claims can be added with [ClaimsBuilder::set].

#
ClaimsBuilder::audience

fn ClaimsBuilder::audience(self : ClaimsBuilder, aud : String) -> ClaimsBuilder

Set the audience claim (intended recipient).

#
ClaimsBuilder::contains

fn ClaimsBuilder::contains(self : ClaimsBuilder, key : String) -> Bool

Check if a claim exists.

#
ClaimsBuilder::expires_at

fn ClaimsBuilder::expires_at(self : ClaimsBuilder, exp : Int) -> ClaimsBuilder

Set the expiration time claim (NumericDate: seconds since epoch).

#
ClaimsBuilder::get

fn ClaimsBuilder::get(self : ClaimsBuilder, key : String) -> Json?

Get a claim value.

#
ClaimsBuilder::issued_at

fn ClaimsBuilder::issued_at(self : ClaimsBuilder, iat : Int) -> ClaimsBuilder

Set the issued-at claim (when the token was issued).

#
ClaimsBuilder::issuer

fn ClaimsBuilder::issuer(self : ClaimsBuilder, iss : String) -> ClaimsBuilder

Set the issuer claim (who issued the token).

#
ClaimsBuilder::jwt_id

fn ClaimsBuilder::jwt_id(self : ClaimsBuilder, jti : String) -> ClaimsBuilder

Set the JWT ID claim (unique identifier for the token).

#
ClaimsBuilder::new

Create a new empty claims builder.

#
ClaimsBuilder::not_before

fn ClaimsBuilder::not_before(self : ClaimsBuilder, nbf : Int) -> ClaimsBuilder

Set the not-before claim (token not valid before this time).

#
ClaimsBuilder::remove

fn ClaimsBuilder::remove(self : ClaimsBuilder, key : String) -> ClaimsBuilder

Remove a claim by key.

#
ClaimsBuilder::set

fn ClaimsBuilder::set(self : ClaimsBuilder, key : String, value : Json) -> ClaimsBuilder

Set a custom claim from a raw JSON value.

#
ClaimsBuilder::set_bool

fn ClaimsBuilder::set_bool(self : ClaimsBuilder, key : String, value : Bool) -> ClaimsBuilder

Set a custom boolean claim.

#
ClaimsBuilder::set_int

fn ClaimsBuilder::set_int(self : ClaimsBuilder, key : String, value : Int) -> ClaimsBuilder

Set a custom integer claim.

#
ClaimsBuilder::set_string

fn ClaimsBuilder::set_string(self : ClaimsBuilder, key : String, value : String) -> ClaimsBuilder

Set a custom string claim.

#
ClaimsBuilder::subject

fn ClaimsBuilder::subject(self : ClaimsBuilder, sub : String) -> ClaimsBuilder

Set the subject claim (who the token is about).

#
ClaimsBuilder::to_json

fn ClaimsBuilder::to_json(self : ClaimsBuilder) -> Json

Build the claims as a JSON value.

#
ClaimsBuilder::to_json_string

fn ClaimsBuilder::to_json_string(self : ClaimsBuilder) -> String

Build the claims as a JSON string.

#
DecodedToken

pub(all) struct DecodedToken {
header : Json
claims : Json
signature : Bytes
raw_header : String
raw_claims : String
raw_signature : String
}

Decoded JWT token parts.

#
DecodedToken::algorithm

fn DecodedToken::algorithm(self : DecodedToken) -> Result[Algorithm, JwtError]

Extract the algorithm from a decoded token's header.

#
DecodedToken::get_numeric_claim

fn DecodedToken::get_numeric_claim(self : DecodedToken, key : String) -> Int?

Get a numeric claim (as Int) from the decoded token.

#
DecodedToken::get_string_claim

fn DecodedToken::get_string_claim(self : DecodedToken, key : String) -> String?

Get a string claim from the decoded token.

#
DecodedToken::key_id

fn DecodedToken::key_id(self : DecodedToken) -> String?

Extract the key ID (kid) from a decoded token's header.

#
DecodedToken::token_type

fn DecodedToken::token_type(self : DecodedToken) -> String?

Extract the token type (typ) from a decoded token's header.

#
JwtError

pub(all) enum JwtError {
InvalidToken(String)
InvalidSignature
InvalidAlgorithm(String)
InvalidHeader(String)
InvalidClaims(String)
TokenExpired
} derive(Eq,
Debug
)

JWT error type.

#
JwtHeader

pub(all) struct JwtHeader {
alg : Algorithm
typ : String
kid : String?
} derive(Eq,
Debug
)

JWT header structure.

#
JwtHeader::hs256

fn JwtHeader::hs256() -> JwtHeader

Default header for HS256.

#
JwtHeader::hs384

fn JwtHeader::hs384() -> JwtHeader

Create a JwtHeader for HS384.

#
JwtHeader::hs512

fn JwtHeader::hs512() -> JwtHeader

Create a JwtHeader for HS512.

#
Sha256

pub(all) struct Sha256 {
h : Array[UInt]
buffer : Array[Byte]
total_len : UInt64
}

SHA-256 state.

#
Sha256::finalize

fn Sha256::finalize(self : Sha256) -> Bytes

Finalize and return the 32-byte digest.

#
Sha256::new

fn Sha256::new() -> Sha256

Create a new SHA-256 hasher.

#
Sha256::write

fn Sha256::write(self : Sha256, data : Bytes) -> Unit

Feed data into the hasher.

#
Sha384

pub(all) struct Sha384 {
inner : Sha512
}

SHA-384 state (uses SHA-512 internally with different IV).

#
Sha384::finalize

fn Sha384::finalize(self : Sha384) -> Bytes

#
Sha384::new

fn Sha384::new() -> Sha384

#
Sha384::write

fn Sha384::write(self : Sha384, data : Bytes) -> Unit

#
Sha512

pub(all) struct Sha512 {
h : Array[UInt64]
buffer : Array[Byte]
total_len : UInt64
}

SHA-512 state.

#
Sha512::finalize

fn Sha512::finalize(self : Sha512) -> Bytes

Finalize and return the 64-byte digest.

#
Sha512::new

fn Sha512::new() -> Sha512

Create a new SHA-512 hasher.

#
Sha512::write

fn Sha512::write(self : Sha512, data : Bytes) -> Unit

Feed data into the hasher.

#
ValidationOptions

pub(all) struct ValidationOptions {
expected_issuer : String?
expected_audience : String?
expected_subject : String?
now_time : Int
leeway : Int
}

Validation options for JWT verification.

#
ValidationOptions::new

fn ValidationOptions::new(now : Int) -> ValidationOptions

Default validation options: no issuer/audience check, leeway=0.

#
ValidationOptions::with_audience

fn ValidationOptions::with_audience(self : ValidationOptions, aud : String) -> ValidationOptions

Set expected audience.

#
ValidationOptions::with_issuer

fn ValidationOptions::with_issuer(self : ValidationOptions, iss : String) -> ValidationOptions

Set expected issuer.

#
ValidationOptions::with_leeway

fn ValidationOptions::with_leeway(self : ValidationOptions, seconds : Int) -> ValidationOptions

Set clock skew leeway in seconds.

#
base64url_decode

fn base64url_decode(s : String) -> Bytes?

Decode a Base64URL string to bytes.

#
base64url_encode

fn base64url_encode(data : Bytes) -> String

Encode bytes to a Base64URL string (no padding).

#
base64url_encode_str

fn base64url_encode_str(s : String) -> String

Encode a string to Base64URL.

#
claim_aud

let claim_aud : String

#
claim_exp

let claim_exp : String

#
claim_iat

let claim_iat : String

#
claim_iss

let claim_iss : String

Standard JWT claim keys (RFC 7519 §4).

#
claim_jti

let claim_jti : String

#
claim_nbf

let claim_nbf : String

#
claim_sub

let claim_sub : String

#
decode

fn decode(token : String) -> Result[DecodedToken, JwtError]

Fully decode a JWT token into its parts (without verifying signature).

Returns the parsed header JSON, claims JSON, and raw signature bytes.

#
decode_claims

fn decode_claims(token : String) -> Result[Json, JwtError]

Decode the claims payload from a JWT token (without verifying signature).

#
hmac_sha256

fn hmac_sha256(key : Bytes, message : Bytes) -> Bytes

Compute HMAC-SHA256 (RFC 2104) and return raw 32-byte digest.

#
hmac_sha384

fn hmac_sha384(key : Bytes, message : Bytes) -> Bytes

Compute HMAC-SHA384 (RFC 2104) and return raw 48-byte digest.

#
hmac_sha512

fn hmac_sha512(key : Bytes, message : Bytes) -> Bytes

Compute HMAC-SHA512 (RFC 2104) and return raw 64-byte digest.

#
is_expired

fn is_expired(token : String, now : Int, leeway : Int) -> Bool

Check if a token is expired without verifying signature.

#
sha256

fn sha256(data : Bytes) -> Bytes

One-shot SHA-256 hash of a byte string.

#
sha256_hex

fn sha256_hex(data : String) -> String

One-shot SHA-256 hash of a MoonBit String, returned as hex.

#
sha384

fn sha384(data : Bytes) -> Bytes

#
sha384_hex

fn sha384_hex(data : String) -> String

SHA-384 hex string output.

#
sha512

fn sha512(data : Bytes) -> Bytes

One-shot SHA-512 hash.

#
sha512_hex

fn sha512_hex(data : String) -> String

SHA-512 hex string output.

#
sign

fn sign(_alg : Algorithm, header_json : String, claims_json : String, secret : Bytes) -> String

Sign a JWT token with HS256 (or HS384/HS512).

header_json and claims_json should be pre-built JSON strings.

#
sign_hs256

fn sign_hs256(header : JwtHeader, claims_json : String, secret : Bytes) -> String

Convenience: sign HS256 with a string secret and pre-made header + claims JSON.

#
sign_hs384

fn sign_hs384(header : JwtHeader, claims_json : String, secret : Bytes) -> String

Sign HS384 with a JwtHeader.

#
sign_hs512

fn sign_hs512(header : JwtHeader, claims_json : String, secret : Bytes) -> String

Sign HS512 with a JwtHeader.

#
sign_with_alg

fn sign_with_alg(alg : Algorithm, header_json : String, claims_json : String, secret : Bytes) -> String

Sign a JWT token with the specified HMAC algorithm.

Automatically selects the correct HMAC function based on the algorithm.

#
validate

fn validate(token : String, secret : Bytes, options : ValidationOptions) -> Result[Json, JwtError]

Validate a token's signature AND claims according to [ValidationOptions].

This is the high-level entry point: it verifies the HMAC signature, then checks exp, nbf, iss, aud, sub claims as configured.

#
validate_with_expiry

fn validate_with_expiry(token : String, secret : Bytes, now : Int) -> Result[Json, JwtError]

Convenience: validate with only signature + expiration check.

#
verify

fn verify(token : String, secret : Bytes) -> Result[Unit, JwtError]

Verify a JWT token's signature. Returns Ok(()) if valid, Err otherwise.

#
verify_with_alg

fn verify_with_alg(token : String, secret : Bytes) -> Result[Algorithm, JwtError]

Verify a token and return the algorithm used.

This decodes the header to determine which HMAC variant was used, then verifies with the corresponding function.