# ywt
ywt is a suite of Gleam-native JWT packages. This is the `ywt_core` package, underpinning the platform-specific libraries.
You should always use it in combination with a platform-specific package.
[](https://hex.pm/packages/ywt_core)
[](https://hexdocs.pm/ywt_core/)
[](https://hex.pm/packages/ywt_erlang)
[](https://hexdocs.pm/ywt_erlang/)
[](https://hex.pm/packages/ywt_webcrypto)
[](https://hexdocs.pm/ywt_webcrypto/)
```sh
gleam add ywt_core@1 ywt_erlang@1 # for erlang
gleam add ywt_core@1 ywt_webcrypto@1 # for javascript
```
ywt signs and verifies JWTs with symmetric and asymmetric algorithms on Erlang
and JavaScript targets.
ywt calls the platform crypto APIs directly (`public_key` on Erlang and
`SubtleCrypto` on JavaScript) through a small FFI layer. The FFI only performs
key generation, signing, and signature verification, so the platform boundary is
short enough to audit; parsing, signing input construction, key decoding, and
claim validation stay in Gleam.
## Supported Algorithms
- `HS256` - HMAC with SHA-256
- `HS384` - HMAC with SHA-384
- `HS512` - HMAC with SHA-512
- `ES256` - ECDSA using the P-256 (secp256r1) curve and SHA-256
- `ES384` - ECDSA using the P-384 (secp384r1) curve and SHA-384
- `ES512` - ECDSA using the P-521 (secp521r1) curve and SHA-512
- `RS256` - RSA PKCS#1 v1.5 with SHA-256
- `RS384` - RSA PKCS#1 v1.5 with SHA-384
- `RS512` - RSA PKCS#1 v1.5 with SHA-512
- `PS256` - RSA PSS with SHA-256
- `PS384` - RSA PSS with SHA-384
- `PS512` - RSA PSS with SHA-512
## Security
JWTs are signed, not encrypted.
Anything in a JWT can be read by whoever has the token, including after the
token expires or is revoked. Do not put secrets, passwords, or sensitive personal
data in the payload.
Always add an expiration claim with `claim.expires_at`. ywt checks `exp`, `nbf`,
and `aud` when they are present even if you did not configure matching claims:
expired tokens are rejected, not-yet-valid tokens are rejected, and tokens with
an `aud` claim are rejected unless you explicitly accept that audience. Audience
validation accepts string and array values.
JWTs have no built-in revocation. If you need logout, account disablement, or
emergency key compromise handling, keep server-side state such as short token
lifetimes, key rotation, a `jti` denylist, or session records.
ywt ignores unknown non-critical JWT header fields and unknown JWK fields while
decoding. Tokens with a `crit` header parameter or `b64: false` are rejected.
Your payload decoder controls which payload fields are accepted. ywt does not
enforce JWK `key_ops` or `use`, validate certificate chains, or support
encrypted or nested JWTs/JWKs.
### General tips
Avoid JWTs unless you need their specific tradeoffs. Server-side sessions or
opaque bearer tokens are usually easier to revoke, rotate, and reason about.
JWTs commonly cause problems around revocation, long-lived leaked tokens, key
rotation, audience confusion, and accidentally exposing data in readable
payloads. They are most useful when multiple services need to verify tokens
without a central lookup on every request. If you mainly need encrypted bearer
tokens, consider [amaro](https://hex.pm/packages/amaro) instead.
Keep signing keys out of source code and rotate them with `kid` values so
verifiers can accept old and new keys during the transition. Prefer asymmetric
keys when services only need to verify tokens; HMAC verification keys are shared
secrets and can also sign tokens.
Only load verification keys from sources you already trust. When rejecting a
token, return the same authentication failure to clients regardless of the
specific error, and consider rate limiting repeated failures.
### Alternatives
If you need encrypted bearer tokens rather than signed JWTs, consider
[amaro](https://hex.pm/packages/amaro), a Gleam package for Fernet and Branca
token encryption.
If you want a less opinionated library with a larger JOSE/JWT surface area,
consider [gose](https://hex.pm/packages/gose), which covers JOSE, JWK, JWT,
and related COSE standards.
## Example (ywt_erlang)
```gleam
import gleam/string
import gleam/dynamic/decode
import gleam/io
import gleam/json
import gleam/time/duration
import ywt
import ywt/algorithm
import ywt/claim
import ywt/verify_key
pub fn main() -> Nil {
// Generate a new, random signing key
let signing_key = ywt.generate_key(algorithm.es384)
// Create user payload data
let payload = [
#("sub", json.string("user123")),
#("role", json.string("admin")),
]
// Define security claims
let claims = [
claim.expires_at(max_age: duration.hours(1), leeway: duration.minutes(5)),
claim.issuer("https://auth.myapp.com", []),
claim.audience("https://api.myapp.com", []),
]
// Create and sign the JWT
let jwt = ywt.encode(payload, claims, signing_key)
io.println("Signed JWT: " <> jwt)
// Extract verification key (for distribution to other services)
let verify_key = verify_key.derived(signing_key)
io.println(
"Public verification key: " <> json.to_string(verify_key.to_jwk(verify_key)),
)
// Verify the JWT
let decoder = {
use id <- decode.field("sub", decode.string)
use role <- decode.field("role", decode.string)
decode.success(#(id, role))
}
let result = ywt.decode(jwt, using: decoder, claims:, keys: [verify_key])
case result {
Ok(#(id, role)) -> {
io.println("JWT verified successfully!")
io.println("User id: " <> id)
io.println("User role: " <> role)
}
Error(ywt.TokenExpired(_expired_at)) -> {
io.println("Token expired!")
}
Error(ywt.InvalidSignature) -> {
io.println("Invalid signature - token may be forged")
}
Error(error) -> {
io.println("JWT verification failed: " <> string.inspect(error))
}
}
}
```
## Development
Tests are shared by both targets and can be run from their individual package directories.
## Resources:
- JWT debugger: [jwt.io](https://jwt.io/)
- JWT (Json Web Tokens): [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)
- JWS (Json Web Signatures): [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515)
- JWK (Json Web Keys): [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)
- JWA (Json Web Algorithms): [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518)
- Elliptic Curve Cryptography: [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480)
- RSA: [RFC 3447](https://datatracker.ietf.org/doc/html/rfc3447)
- HMAC: [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104)
- Erlang `public_key` module: [Docs](https://www.erlang.org/doc/apps/public_key/public_key.html)
- Erlang-JOSE : [github](https://github.com/potatosalad/erlang-jose/)
- WebCrypto/SubtleCrypto: [MDN](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
- jsonwebtoken library: [npm](https://www.npmjs.com/package/jsonwebtoken)