ywt
Types
Detailed error information for JWT parsing failures.
This provides specific information about why JWT validation failed, enabling better error handling, logging, and debugging. Different error types allow you to respond appropriately - for example, expired tokens might trigger token refresh, while signature failures indicate potential attacks.
Error Categories
- Format Errors: Malformed JWT structure or encoding issues
- Signature Errors: Cryptographic validation failures
- Claim Errors: Business logic validation failures
- Key Errors: Issues with cryptographic keys
Usage Example
case parse(jwt, using: decoder, claims: claims, keys: keys) {
Ok(payload) -> {
// Success - use the payload
next(payload)
}
Error(TokenExpired(expired_at)) -> {
// Handle expired token - maybe refresh
log.info("Token expired at: " <> timestamp.to_string(expired_at))
redirect_to_refresh()
}
Error(InvalidSignature) -> {
// Potential security issue - log and reject
log.warning("Invalid JWT signature detected")
unauthorized()
}
Error(InvalidIssuer(expected, actual)) -> {
// Wrong issuer - reject but log for debugging
log.error("Wrong issuer - expected: " <> expected <> ", got: " <> actual)
forbidden()
}
Error(_) -> {
// Generic handling for other errors
bad_request()
}
}
pub type ParseError {
MalformedToken
InvalidHeaderEncoding
InvalidPayloadEncoding
InvalidSignatureEncoding
InvalidHeaderJson(json.DecodeError)
InvalidPayloadJson(json.DecodeError)
NoMatchingKey
InvalidSignature
TokenExpired(expired_at: timestamp.Timestamp)
TokenNotYetValid(not_before: timestamp.Timestamp)
InvalidIssuer(expected: List(String), actual: String)
InvalidAudience(expected: List(String), actual: String)
InvalidSubject(expected: List(String), actual: String)
InvalidId(expected: List(String), actual: String)
MissingClaim(claim_name: String)
ClaimDecodingError(
claim_name: String,
error: List(decode.DecodeError),
)
InvalidCustomClaim(claim_name: String)
PayloadDecodingError(List(decode.DecodeError))
}
Constructors
-
MalformedTokenJWT doesn’t contain exactly 3 parts separated by dots
-
InvalidHeaderEncodingBase64 decoding failed for header
-
InvalidPayloadEncodingBase64 decoding failed for payload
-
InvalidSignatureEncodingBase64 decoding failed for signature
-
InvalidHeaderJson(json.DecodeError)JSON parsing or decoding failed for header
-
InvalidPayloadJson(json.DecodeError)JSON parsing failed for payload - note that this does not include failures for your payload decoder. See
PayloadDecodingError. -
NoMatchingKeyNo suitable key found to verify the signature
-
InvalidSignatureSignature verification failed (potential tampering)
-
TokenExpired(expired_at: timestamp.Timestamp)Token has expired
-
TokenNotYetValid(not_before: timestamp.Timestamp)Token is not yet valid (nbf claim)
-
InvalidIssuer(expected: List(String), actual: String)Wrong issuer
-
InvalidAudience(expected: List(String), actual: String)Wrong audience
-
InvalidSubject(expected: List(String), actual: String)Wrong audience
-
InvalidId(expected: List(String), actual: String) -
MissingClaim(claim_name: String)Required claim is missing
-
ClaimDecodingError( claim_name: String, error: List(decode.DecodeError), )Claim Decoding Error
-
InvalidCustomClaim(claim_name: String) -
PayloadDecodingError(List(decode.DecodeError))Payload structure doesn’t match expected decoder
Values
pub fn decode(
jwt jwt: String,
using decoder: decode.Decoder(payload),
claims claims: List(claim.Claim),
keys keys: List(verify_key.VerifyKey),
) -> Result(payload, ParseError)
Verifies a JWT signature and validates all claims, returning the decoded payload if successful.
Use this to validate incoming JWTs from clients, ensuring they’re authentic and haven’t been tampered with.
Security Considerations
- Implement appropriate claims validation (expiration, issuer, audience)
- Handle verification failures securely (don’t leak information)
- Consider rate limiting to prevent brute force attacks
Example
// Define expected claims for validation
let claims = [
claim.expires_at(max_age: duration.hours(1), leeway: duration.minutes(5)),
claim.issuer("my-app", []),
]
// Parse and validate the JWT
case ywt.decode(jwt_token, using: payload_decoder, claims:, keys: [verify_key]) {
Ok(payload) -> {
// JWT is valid, use the payload
let user_id = // extract user ID from payload
authorize_request(user_id)
}
Error(_) -> {
// JWT is invalid - reject the request
unauthorized_response()
}
}
💡 Best Practice: Always validate JWTs on every request and never trust client-provided tokens without verification.
pub fn decode_unsafely_without_validation(
jwt: String,
payload_decoder: decode.Decoder(payload),
) -> Result(payload, Nil)
Extracts payload from a JWT without verifying its signature or claims.
🚨 USE WITH EXTREME CAUTION Only use this when you need to inspect tokens from trusted sources where signature verification is handled elsewhere.
Security Considerations
- NEVER use this in production for authentication
- Tokens could be forged or tampered with
- No expiration or claim validation is performed
- Only use when signature validation happens at a different layer
- Consider this as dangerous as accepting any user input
pub fn encode(
payload payload: List(#(String, json.Json)),
claims claims: List(claim.Claim),
key key: sign_key.SignKey,
) -> String
Creates a signed JWT containing the specified payload and claims.
Signed JWTs prevent actors without access to the SignKey from modifying it. This can be verified using the corresponding VerifyKey.
Security Considerations
- JWTs are signed, not encrypted - all data is publicly readable
- Never include sensitive data like passwords or personal information
- Keep payloads small to avoid large tokens
- Include appropriate expiration times in your claims
- Use strong signing keys and rotate them regularly
If a field is present in claims as well as the payload, the payload takes precedence.
Example
// Create a user session token
let payload = [
#("sub", json.string("user_12345")),
#("role", json.string("developer")),
#("permissions", json.array([json.string("read"), json.string("write")]))
]
let claims = [
claim.issued_at(),
claim.expires_at(max_age: duration.hours(1), leeway: duration.minutes(5)),
claim.issuer("my-app", []),
]
ywt.encode(payload: payload, claims: claims, key: sign_key)
// Returns: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ..."
⚠️ Remember: Anyone can decode and read JWT contents. Only include data you’re comfortable being public.
pub fn generate_key(
algorithm: algorithm.Algorithm,
) -> sign_key.SignKey
Generates a new cryptographically secure signing key for the specified algorithm.
This function creates fresh signing keys with appropriate parameters for each algorithm type. All keys are generated using cryptographically secure random number generators and follow current security best practices.
The generated keys will have a random id set that can be used to identify the key during key rotation.
Usage
Use this for key generation in development, testing, or when implementing key rotation systems. For production use, consider generating keys offline and storing them in secure key management systems.
Key Parameters
- HMAC: Full-entropy random secrets of appropriate length (at least 32/48/64 bytes)
- RSA: 4096-bit modulus with secure random prime generation
- ECDSA: Secure random private scalars on specified curves. The digest type matches the selected curve.
pub fn sign_bits(
message: BitArray,
key: sign_key.SignKey,
) -> BitArray
Creates a cryptographic signature for the given message using the specified signing key.
This is a low-level function that performs the actual cryptographic signing operation.
Usage
This function is primarily used internally by higher-level JWT functions. Use this directly only when you need raw signature operations outside of JWT context.
Security Considerations
- Never sign untrusted or unvalidated input data
- Ensure signing keys are stored securely and accessed only by authorized code
Example
let message = <<"Hello, world!":utf8>>
let signing_key = load_secure_signing_key()
let signature = ywt.sign_bits(message, signing_key)
// signature is raw bytes that can be verified with corresponding verify key
⚠️ Warning: This function performs raw cryptographic operations. Most applications should use the higher-level JWT functions instead.
pub fn sign_string(
message: String,
key: sign_key.SignKey,
) -> String
Creates a base64url-encoded signature for a UTF-8 string message.
This is a convenience wrapper around sign_bits that handles string encoding
and base64url encoding of the signature, commonly used for signing JWT components.
Usage
Use this when you need to sign string data and want the signature in base64url format for use in web contexts like JWTs or HTTP headers.
Security Considerations
- Same security considerations as
sign_bitsapply - The string is encoded as UTF-8 before signing
- Verify signatures using the corresponding
verify_stringfunction
Example
let jwt_payload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0"
let signing_key = load_jwt_signing_key()
let signature = sign_string(jwt_payload, signing_key))
// signature is base64url-encoded string ready for JWT use
💡 Note: The returned signature is base64url-encoded (URL-safe, no padding) as required by JWT and other web standards.
pub fn verify_bits(
message: BitArray,
signature: BitArray,
key: verify_key.VerifyKey,
) -> Bool
Verifies a cryptographic signature against a message using the specified verification key.
This is a low-level function that performs the actual cryptographic verification.
Returns True if the signature is valid for the given message and key, False otherwise.
Usage
Use this for raw signature verification operations. The verification algorithm is determined by the key type and must match the algorithm used for signing.
Example
let message = <<"Hello, world!":utf8>>
let signature = // ... received signature bytes
let verify_key = load_verification_key()
let is_valid = verify_bits(message, signature, verify_key)
case is_valid {
True -> process_verified_message(message)
False -> reject_invalid_signature()
}
🔒 Critical: Never trust data with invalid signatures. A False result
indicates potential tampering or use of wrong keys.
pub fn verify_string(
message: String,
signature: String,
key: verify_key.VerifyKey,
) -> Bool
Verifies a base64url-encoded signature against a UTF-8 string message.
This is a convenience wrapper around verify_bits that handles string encoding
and base64url decoding of the signature. Commonly used for verifying JWT signatures.
Usage
Use this when verifying signatures that are base64url-encoded, such as those from JWTs or other web-based cryptographic protocols.
Security Considerations
- Same security considerations as
verify_bitsapply - Invalid base64url encoding in the signature automatically returns
False
Example
let payload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0"
let signature = "base64url-encoded-signature-string"
let verify_key = load_jwt_verification_key()
let is_valid = verify_string(payload, signature, verify_key)
case is_valid {
True -> {
// Signature is valid, payload can be trusted
process_authenticated_request(payload)
}
False -> {
// Signature invalid - reject the request
return_authentication_error()
}
}
⚠️ Important: Malformed base64url signatures return False rather than
causing errors, ensuring consistent handling of invalid input.