Quickstart
signet gives you three things for Fluid Framework document tokens: the claim types, HS256 signing and verification, and a set of claim validators. This page takes you from zero to a verified, validated token.
Install
Section titled “Install”signet is not published to Hex. You depend on it by pinning a commit in your
gleam.toml — so every build resolves the exact same token code, byte for byte.
[dependencies]signet = { git = "https://github.com/tylerbutler/signet.git", ref = "6ea697d3d1d6c5ab60e214414fbfbb134b355a8c" }Then fetch it:
gleam deps downloadMint a token
Section titled “Mint a token”mint_token builds a strict HS256 token (version "1.0") with a random jti.
Scopes are typed — import the ones you need from signet/types.
import signet/jwtimport signet/types.{DocRead, DocWrite}
pub fn issue(secret: String, now: Int) -> String { jwt.mint_token( "tenant-1", // tenant "doc-1", // document id [DocRead, DocWrite], // scopes "user-1", // user id secret, now, 3600, // expires in (seconds) )}Verify a signature
Section titled “Verify a signature”verify_signature checks the HS256 signature with a constant-time comparison
and parses the payload into TokenClaims. It does not check tenant,
document, or expiry — that is the validators’ job, kept deliberately separate.
import signet/jwtimport signet/types.{type TokenClaims}
pub fn read_claims(token: String, secret: String) -> Result(TokenClaims, Nil) { case jwt.verify_signature(token, secret) { Ok(claims) -> Ok(claims) Error(jwt.BadSignature) -> Error(Nil) Error(jwt.BadFormat) -> Error(Nil) }}Reading a token straight off an incoming request? extract_token pulls the JWT
out of an Authorization header — both the Routerlicious Basic scheme and a
conventional Bearer scheme.
import signet/jwt
let assert Ok(token) = jwt.extract_token("Bearer " <> raw_jwt)Validate the claims
Section titled “Validate the claims”Once you trust the signature, check the claims against the request. The
validators compose: validate_write_access runs the connection checks
(expiry → tenant → document), then read scope, then write scope — in that order.
import gleam/resultimport signet/jwt
pub fn authorize_write( token: String, secret: String, tenant: String, document: String, now: Int,) -> Result(Nil, jwt.JwtValidationError) { use claims <- result.try( jwt.verify_signature(token, secret) |> result.replace_error(jwt.MissingClaim("signature")), ) jwt.validate_write_access(claims, tenant, document, now)}When validation fails, the error carries what to say and how to answer:
case jwt.validate_write_access(claims, tenant, document, now) { Ok(Nil) -> serve() Error(err) -> { let status = jwt.error_to_http_code(err) // 401 or 403 let message = jwt.format_error(err) // human-readable reason reject(status, message) }}Try it live
Section titled “Try it live”Want to see the bytes? The token playground mints and verifies
real signet tokens in your browser — paste a secret, watch the
header.payload.signature take shape, then tamper with it and watch validation
catch it.
- signet/types — the claim, user, and scope types.
- signet/jwt — every crypto and validation function, with signatures.