jwt
The jwt library provides compact JWT parsing, JWS signing and
verification, JWK/JWKS key selection, and reusable registered-claim
validation predicates.
The current implementation supports native HS256
signing/verification and OpenSSL-backed RS256/ES256
verification. ES256 signatures are converted from JOSE raw
R || S encoding to DER before calling OpenSSL.
This library requires a Prolog backend supporting unbound integer arithmetic.
Requirements
The openssl command must be available on the current PATH.
On macOS, it can be installed using e.g. Homebrew:
$ brew install openssl
Or using MacPorts:
$ sudo port install openssl
On Ubuntu, it can be installed using:
$ sudo apt install openssl
On RedHat distributions (8.x and later):
$ sudo dnf install openssl
For older RedHat distributions:
$ sudo yum install openssl
On Windows, it can be installed using e.g. Chocolatey:
> choco install openssl
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(jwt(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(jwt(tester)).
Options
The facade predicates that take an options list validate the following option terms. Each option only affects predicates that perform the corresponding operation.
Signature and algorithm options:
allow_algorithms(Algorithms)- List of allowed JOSEalgatom values for verification and signing. The default is['HS256', 'RS256', 'ES256'].algorithm(Algorithm)- Requires one exact JOSEalgatom value. When used withallow_algorithms/1, both checks must pass.allow_none(Boolean)- Allows or rejects the unsecurednonealgorithm. The default isfalse.
Claim validation options:
allow_missing_exp(Boolean)- Allows tokens without anexpclaim when set totrue. The default isfalse.clock_skew(Seconds)- Non-negative numeric leeway, in seconds, for time claim validation. The default is60.now(Timestamp)- Numeric timestamp to use instead of the current operating system time when validating time claims.max_age(Seconds)- Non-negative maximum age, in seconds, for policies that validate aniatclaim astime(issued_at).required_claims(Names)- List of claim-name atoms that must be present when validating claims. The default is[].claim_policy(Policy)- Claim policy list applied byverify/4andverify/5after signature verification. When callingvalidate_claims/3directly, pass the policy as the second argument instead.
OpenSSL options:
openssl_executable(Executable)- Atom naming the OpenSSL executable or its path. The default command name isopenssl.openssl_arguments(Arguments)- List accepted by the option checker for OpenSSL command customization. The current facade implementation does not add these arguments to the generated OpenSSL command.
Validation policy
The verification predicates apply a conservative JWT validation policy in addition to signature verification:
JWT headers and claims must decode to JSON objects. Objects with duplicate member names are rejected instead of using first-wins or last-wins semantics.
JWS
critheader parameters are rejected unless explicitly supported by the library. The current implementation does not support extension header parameters that can be marked critical.The
expclaim is required by default. This is stricter than the base JWT specification, where registered claims are optional. Use the optionallow_missing_exp(true)when verifying tokens whose issuer legitimately omits this claim.Use
allow_algorithms/1to configure the allowed algorithm set. Usealgorithm/1when the caller expects one exact JOSEalgvalue. When both options are present, both constraints must be satisfied.When verifying with a JWK Set, all keys matching the token header
algand optionalkidare tried in set order until a signature verifies. Verification fails only after all matching candidate keys fail.HS256symmetric keys must contain at least 32 bytes of key material, matching the algorithm’s 256-bit security requirement.RSA JWK
nandemembers must be minimally encoded positive Base64urlUInt values.RS256public moduli must be odd and at least 2048 bits, and public exponents must be odd integers greater than one and smaller than the modulus.P-256 EC JWK coordinates must be 32-byte unsigned values in range and the point
(x,y)must lie on the P-256 curve.Octet, RSA, and P-256 EC JWKs are validated before key material is used. Malformed key objects raise JWT key-domain errors.
OpenSSL-backed RS256 and ES256 verification requires an
openssl command in the current command search path, unless
overridden using openssl_executable/1.
Basic usage
Decode a compact JWT without verifying its signature:
| ?- jwt::decode(Token, Header, Claims).
Inspect only selected header or claims data:
| ?- jwt::peek_algorithm(Token, Algorithm).
| ?- jwt::claim(Claims, sub, Subject).
Sign and verify a token using native HS256 support:
| ?- Header = {alg-'HS256', typ-'JWT'},
Claims = {sub-'123', exp-4102444800},
jwt::sign(Header, Claims, '0123456789abcdef0123456789abcdef', Token, []),
jwt::verify(Token, '0123456789abcdef0123456789abcdef', VerifiedClaims, []).
Validate claims independently from signature verification:
| ?- Policy = [
claim(iss, expected('https://issuer.example')),
claim(aud, contains('client-id')),
claim(exp, time(expiration))
],
jwt::validate_claims(Claims, Policy, [now(1700000001)]).
Verify an asymmetric token using a JWK Set. The library selects matching
keys using the JWT header alg and optional kid values:
| ?- JWKSet = {keys-[PublicJWK]},
jwt::verify(Token, JWKSet, Claims, [
allow_algorithms(['RS256', 'ES256']),
claim_policy([
claim(iss, expected('https://issuer.example')),
claim(aud, contains('client-id'))
])
]).
Use the openssl_executable/1 option when the OpenSSL command is not
named openssl or is not found in the default command search path:
| ?- jwt::verify(Token, JWKSet, Claims, [
openssl_executable('/opt/local/bin/openssl')
]).