///|
/// Decoded JWT token parts.
pub(all) struct DecodedToken {
header : Json
claims : Json
signature : Bytes
raw_header : String
raw_claims : String
raw_signature : String
}
///|
/// Parse the header JSON from a decoded token.
fn parse_header_json(header_b64 : String) -> Result[Json, JwtError] {
let header_bytes = match base64url_decode(header_b64) {
None => return Err(InvalidHeader("cannot decode header"))
Some(b) => b
}
let header_str = @utf8.decode_lossy(header_bytes)
Ok(@json.parse(header_str)) catch {
_ => Err(InvalidHeader("header is not valid JSON"))
}
}
///|
/// Fully decode a JWT token into its parts (without verifying signature).
///
/// Returns the parsed header JSON, claims JSON, and raw signature bytes.
pub fn decode(token : String) -> Result[DecodedToken, JwtError] {
let parts = token.split(".").to_array()
if parts.length() != 3 {
return Err(InvalidToken("token must have 3 parts"))
}
let header_b64 = parts[0].to_owned()
let claims_b64 = parts[1].to_owned()
let sig_b64 = parts[2].to_owned()
// Parse header
let header_json = match parse_header_json(header_b64) {
Ok(h) => h
Err(e) => return Err(e)
}
// Parse claims
let claims_json : Json = match base64url_decode(claims_b64) {
None => return Err(InvalidClaims("cannot decode claims"))
Some(claims_bytes) => {
let claims_str = @utf8.decode_lossy(claims_bytes)
@json.parse(claims_str) catch {
_ => return Err(InvalidClaims("claims is not valid JSON"))
}
}
}
// Decode signature
let sig_bytes = match base64url_decode(sig_b64) {
None => return Err(InvalidToken("cannot decode signature"))
Some(b) => b
}
Ok({
header: header_json,
claims: claims_json,
signature: sig_bytes,
raw_header: header_b64,
raw_claims: claims_b64,
raw_signature: sig_b64,
})
}
///|
/// Extract the algorithm from a decoded token's header.
pub fn DecodedToken::algorithm(
self : DecodedToken,
) -> Result[Algorithm, JwtError] {
match self.header {
Object(o) =>
match o["alg"] {
String(s) =>
match s {
"HS256" => Ok(HS256)
"HS384" => Ok(HS384)
"HS512" => Ok(HS512)
_ => Err(InvalidAlgorithm(s))
}
_ => Err(InvalidHeader("alg field is not a string"))
}
_ => Err(InvalidHeader("header is not an object"))
}
}
///|
/// Extract the key ID (kid) from a decoded token's header.
pub fn DecodedToken::key_id(self : DecodedToken) -> String? {
match self.header {
Object(o) =>
match o["kid"] {
String(s) => Some(s)
_ => None
}
_ => None
}
}
///|
/// Extract the token type (typ) from a decoded token's header.
pub fn DecodedToken::token_type(self : DecodedToken) -> String? {
match self.header {
Object(o) =>
match o["typ"] {
String(s) => Some(s)
_ => None
}
_ => None
}
}
///|
/// Get a string claim from the decoded token.
pub fn DecodedToken::get_string_claim(
self : DecodedToken,
key : String,
) -> String? {
match self.claims {
Object(o) =>
match o[key] {
String(s) => Some(s)
_ => None
}
_ => None
}
}
///|
/// Get a numeric claim (as Int) from the decoded token.
pub fn DecodedToken::get_numeric_claim(
self : DecodedToken,
key : String,
) -> Int? {
match self.claims {
Object(o) =>
match o[key] {
Number(n, ..) => Some(n.to_int())
_ => None
}
_ => None
}
}