ywt/claim

Types

A rule that validates or writes a JWT header or payload field.

Required claims must be present when decoding. Optional claims are checked only when the token includes the corresponding field.

pub opaque type Claim

Values

pub fn audience(primary: String, others: List(String)) -> Claim

The aud claim identifies who the token is meant for.

Tokens with an aud field are rejected by default unless you add this claim and the value matches one of the accepted audiences. ywt validates both string and array-valued aud fields.

let claims = [
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn custom(
  name name: String,
  value value: a,
  encode encode: fn(a) -> json.Json,
  decoder decoder: decode.Decoder(a),
) -> Claim

A custom claim stores an application-specific value.

The decoded token value must exactly match value. Use this for simple fixed requirements such as role, tenant, or token class checks.

let claims = [
  claim.custom("role", "admin", json.string, decode.string),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn encode(
  claims: List(Claim),
  data: List(#(String, json.Json)),
) -> json.Json

Encodes claims and application data into a JSON object.

This is a low-level function used internally by ywt.

Claims take precedence when a field appears in both lists. Put security fields such as exp, iss, aud, and sub in claims instead of payload data.

let claims = [
  claim.subject("user_123", []),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
]

claim.encode(claims, [
  #("sub", json.string("ignored")),
  #("role", json.string("admin")),
])
pub fn encode_numeric_date(
  timestamp: timestamp.Timestamp,
) -> json.Json

Encodes a timestamp as a JWT numeric date.

Fractional seconds are truncated to keep encoded JWTs compact.

#("checked_at", claim.encode_numeric_date(timestamp.system_time()))
pub fn expires_at(
  max_age max_age: duration.Duration,
  leeway leeway: duration.Duration,
) -> Claim

The exp claim says when the token expires.

Prefer short lifetimes. Tokens with exp are checked by default with zero leeway, but ywt will not add an expiration unless you include this claim. max_age is used when writing the token; verification checks the token’s exp value plus leeway.

let claims = [
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn id(id: String, others: List(String)) -> Claim

The jti claim gives the token a unique id.

ywt does not store token ids. Use this with your own denylist or session store if you need revocation.

let claims = [
  claim.id("session-123", []),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn issued_at() -> Claim

The iat claim records when the token was issued.

This writes the current time when creating a token, but it does not reject future-dated tokens when verifying. Use not_before for that.

let claims = [
  claim.issued_at(),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn issuer(issuer: String, others: List(String)) -> Claim

The iss claim identifies who issued the token.

Use this to reject tokens from the wrong issuer. Issuer strings often point at the service that also publishes verification keys, but ywt does not fetch or trust those keys automatically.

let claims = [
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn not_before(
  time time: timestamp.Timestamp,
  leeway leeway: duration.Duration,
) -> Claim

The nbf claim says the token must not be accepted before a time.

Tokens with nbf are checked by default with zero leeway. Add this claim when you want to set the value while signing or allow clock skew while verifying. Keep leeway small.

let claims = [
  claim.not_before(timestamp.system_time(), leeway: duration.minutes(1)),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn numeric_date_decoder() -> decode.Decoder(
  timestamp.Timestamp,
)

Decodes a JWT numeric date into a timestamp.

JWT numeric dates are seconds since the Unix epoch.

decode.field("exp", claim.numeric_date_decoder())
pub fn optional(claim: Claim) -> Claim

Allows a claim to be absent during verification.

When the field is present it is still validated. If the claim is already optional, it is returned unchanged.

let claims = [
  claim.id("session-123", []) |> claim.optional,
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn subject(subject: String, others: List(String)) -> Claim

The sub claim identifies the user or entity the token is about.

Prefer stable internal ids. Avoid putting personal data in the subject, because JWT payloads are readable by whoever has the token.

let claims = [
  claim.subject("user_123", []),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn typ(expected: String) -> Claim

The typ header describes what kind of token this is.

The field is optional in the JWT specification, but some systems require "JWT" or another explicit type. It is not a security boundary; still validate issuer, audience, expiration, and signature.

let claims = [
  claim.typ("JWT"),
  claim.expires_at(max_age: duration.minutes(15), leeway: duration.minutes(1)),
  claim.issuer("https://auth.example.com", []),
  claim.audience("https://api.example.com", []),
]
pub fn verify(
  payload: dynamic.Dynamic,
  claims: List(Claim),
) -> Result(Nil, @internal Error)

Validates claims against a JWT payload.

This is a low-level function used internally by ywt.

This does not verify the token signature. Use the platform ywt.decode function for normal JWT verification.

Tokens with exp and nbf are checked by default with zero leeway. Tokens with aud are rejected by default unless you pass an audience claim. Audience validation accepts string or array values.

Search Document