module

KemalIdentity::Testing::JWTForge

Mints JSON Web Tokens, including ones no honest issuer would produce.

The shard validates tokens and deliberately does not issue them, so this lives in kemal_identity/testing rather than the production entry point. It exists to attack the validator: every knob a forger would want — the algorithm, the kid, an unsigned token, a header that lies about what the signature covers, arbitrary bytes in any segment — is reachable from here, because a validator spec that can only produce well-formed tokens tests nothing an attacker would ever send.

Constants

AUDIENCE = "https://api.example.com"
DIGESTS = {"HS256" => ::OpenSSL::Algorithm::SHA256, "HS384" => ::OpenSSL::Algorithm::SHA384, "HS512" => ::OpenSSL::Algorithm::SHA512}
ISSUER = "https://issuer.example.com"
SECRET = KemalIdentity::Secret.new("k" * 64)

A key long enough for every HMAC variant this shard ships.

Class methods

claims(now : Time = KemalIdentity::Testing::FIXED_NOW, subject : String = "a1", expires_in : Time::Span | Nil = 15.minutes, issuer : String | Nil = ISSUER, audience : JSON::Any | Nil = ::JSON::Any.new(AUDIENCE), purpose : String | Nil = "access", issued_at : Time | Nil = nil, jti : String | Nil = nil) : Hash(String, JSON::Any)
Source
encode(claims : Hash(String, JSON::Any), secret : KemalIdentity::Secret = SECRET, algorithm : String = "HS256", kid : String | Nil = nil, header : Hash(String, JSON::Any) = {} of String => ::JSON::Any) : String

A signed token. algorithm names the alg header and selects the digest, so passing one the key was not meant for produces exactly the confusion attack.

Source
encode_bytes(bytes : Bytes) : String
Source
encode_lying(claims : Hash(String, JSON::Any), claimed : String, signed_with : String, secret : KemalIdentity::Secret = SECRET, kid : String | Nil = nil) : String

A token whose header claims one algorithm while the signature was made with another.

Source
encode_rsa(claims : Hash(String, JSON::Any), algorithm : String = "RS256", kid : String | Nil = nil, header : Hash(String, JSON::Any) = {} of String => ::JSON::Any) : String

An RS256/384/512 token signed by the fixed test key.

Source
segment(json : String) : String
Source
swap_claims(token : String, claims : Hash(String, JSON::Any)) : String

Replaces the claims of an already-signed token, leaving its signature untouched.

Source
unsigned(claims : Hash(String, JSON::Any), signature : String = "", algorithm : String = "none") : String

The alg: none token: a real header, real claims, and an empty signature segment. Also produced with a non-empty junk signature, since some libraries only check that the segment is present.

Source