@molecule/api-jwt
Core interface · jwt · API (Node) · v1.0.7 · Apache-2.0
JSON Web Token interface for molecule.dev
npm install @molecule/api-jwtHow it works
@molecule/api-jwt is the jwt core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/api-jwt-jsonwebtoken.
import { sign, verify, decode } from '@molecule/api-jwt'
const token = sign({ userId }, { expiresIn: '15m' }) // server-side; expiry set
try {
const claims = verify(token) as JwtPayload // signature CHECKED — safe to trust
grantAccess(claims.userId)
} catch {
res.status(401).json({ error: 'Invalid or expired token.' })
}
decode(token) // NOT verified — never use its output for an auth decisionProviders (1): @molecule/api-jwt-jsonwebtoken
Works with: @molecule/api-bond
Reference
JWT interface for molecule.dev.
Provides an abstract JWT interface that can be backed by any JWT library.
Use setProvider to provide a concrete implementation
such as @molecule/api-jwt-jsonwebtoken.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { sign, verify, decode } from '@molecule/api-jwt'
const token = sign({ userId }, { expiresIn: '15m' }) // server-side; expiry set
try {
const claims = verify(token) as JwtPayload // signature CHECKED — safe to trust
grantAccess(claims.userId)
} catch {
res.status(401).json({ error: 'Invalid or expired token.' })
}
decode(token) // NOT verified — never use its output for an auth decision
Type
core
Installation
npm install @molecule/api-jwt @molecule/api-bond
API
Interfaces
JwtSignOptions
Options for signing a JWT.
interface JwtSignOptions {
algorithm?: JwtAlgorithm
expiresIn?: number | string
notBefore?: number | string
audience?: string | string[]
issuer?: string
subject?: string
jwtid?: string
keyid?: string
header?: Record<string, unknown>
}
JwtVerifyOptions
Options for verifying a JWT.
Note: security-hardened bonds (e.g. @molecule/api-jwt-jsonwebtoken)
REFUSE to honor ignoreExpiration/ignoreNotBefore — an expired token
always fails verification regardless of these flags. To tolerate clock
skew or slow flows, use clockTolerance (seconds) instead.
interface JwtVerifyOptions {
algorithms?: JwtAlgorithm[]
audience?: string | string[]
issuer?: string | string[]
subject?: string
clockTolerance?: number
maxAge?: string | number
complete?: boolean
ignoreExpiration?: boolean
ignoreNotBefore?: boolean
}
JwtDecodeOptions
Options for decoding a JWT (without verification).
interface JwtDecodeOptions {
complete?: boolean
json?: boolean
}
JwtPayload
Decoded JWT payload.
interface JwtPayload {
[key: string]: unknown
iss?: string
sub?: string
aud?: string | string[]
exp?: number
nbf?: number
iat?: number
jti?: string
}
JwtProvider
JWT provider interface that all JWT bond packages must implement.
Provides sign, verify, and decode operations. Key management
and algorithm configuration are handled by the core package.
interface JwtProvider {
sign(payload: JSONObject, options?: JwtSignOptions, privateKey?: string | Buffer): string
verify(
token: string,
options?: JwtVerifyOptions,
publicKey?: string | Buffer,
): string | JwtPayload
decode(token: string, options?: JwtDecodeOptions): string | JwtPayload | null
}
Types
JwtAlgorithm
Supported JWT signing algorithms.
type JwtAlgorithm =
| 'RS256'
| 'RS384'
| 'RS512'
| 'HS256'
| 'HS384'
| 'HS512'
| 'ES256'
| 'ES384'
| 'ES512'
| 'PS256'
| 'PS384'
| 'PS512'
JSONValue
Recursive JSON value type representing any valid JSON primitive, array, or object.
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }
JSONObject
A plain JSON object whose values are JSONValues. Used as the payload
type for JWT signing operations.
type JSONObject = { [key: string]: JSONValue }
Functions
generateKeyPairSync()
Generates an RSA-2048 key pair in PEM format for JWT signing and verification.
function generateKeyPairSync(): { publicKey: string; privateKey: string }
Returns: An object containing publicKey and privateKey as PEM strings.
writeKeys(outputPath)
Writes a freshly generated RSA key pair to disk as PEM files. Creates the output directory if it does not exist.
function writeKeys(outputPath?: string): void
outputPath— Directory to write the PEM files into; defaults to{JWT_KEYS_DIR}/{NODE_ENV}/.
setProvider(provider)
Registers a JWT provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: JwtProvider): void
provider— The JWT provider implementation to bond.
getProvider()
Retrieves the bonded JWT provider, throwing if none is configured.
function getProvider(): JwtProvider
Returns: The bonded JWT provider.
hasProvider()
Checks whether a JWT provider is currently bonded.
function hasProvider(): boolean
Returns: true if a JWT provider is bonded.
sign(object, options, privateKey)
Signs a payload into a JWT string using the bonded provider. Uses the configured algorithm, expiry, and private key as defaults.
This is the LOW-LEVEL signer: it signs whatever payload it is given, and a
token it mints is not by itself a session. For a session, use
authorization.set() from @molecule/api-resource-user — the app's global
verifyMiddleware accepts a JWT only when its payload also carries a
deviceId naming an active row in devices (see the module @remarks).
The sequence is device → session, and the device call RESOLVES TO THE ID:
const deviceId = await get<DeviceService>('device')?.createOrUpdate(userId, deviceName)
(DeviceService is exported by @molecule/api-resource-device), then
authorization.set(req, res, { userId, deviceId }).
There is ONE JWT provider per app, and this signs with it. The scaffold
bonds @molecule/api-jwt-jsonwebtoken once (the app's api/src/bonds/), keyed
by JWT_PRIVATE_KEY/JWT_PUBLIC_KEY (auto-generated on first boot when unset)
with algorithm JWT_ALGORITHM (default RS256). There is no JWT_SECRET and
no second verifier to match: setProvider() in your own file REPLACES the
provider for the whole process, so every token this app and its packages mint
and verify changes with it.
To return a session token in a response BODY — an API whose clients send
Authorization: Token <jwt> — do not sign a second token with another key or
algorithm. authorization.set(req, res, { userId, deviceId }) from
(description truncated — read the full text in node_modules/@molecule/api-jwt/dist/provider.d.ts.)
function sign(
object: JSONObject,
{ algorithm = JWT_ALGORITHM, expiresIn = JWT_EXPIRES_TIME, ...rest }?: JwtSignOptions,
privateKey?: string | Buffer<ArrayBufferLike>,
): string
object— The JSON payload to sign.options— Signing options;algorithmdefaults toJWT_ALGORITHM,expiresIndefaults toJWT_EXPIRES_TIME.options.algorithm— The signing algorithm (e.g.RS256,HS256).options.expiresIn— Token lifetime in seconds.privateKey— The private key for signing; defaults toJWT_PRIVATE_KEY.
Returns: The signed JWT string.
verify(token, options, publicKey)
Verifies a JWT string and returns the decoded payload. Uses the configured algorithm and public key as defaults.
function verify(
token: string,
{ algorithms = [JWT_ALGORITHM], ...rest }?: JwtVerifyOptions,
publicKey?: string | Buffer<ArrayBufferLike>,
): string | JwtPayload
token— The JWT string to verify.options— Verification options;algorithmsdefaults to[JWT_ALGORITHM].options.algorithms— The allowed signing algorithms for verification.publicKey— The public key for verification; defaults toJWT_PUBLIC_KEY.
Returns: The decoded payload string or object.
decode(token, options)
Decodes a JWT string without verifying its signature. Useful for inspecting token contents when verification is handled elsewhere.
function decode(token: string, options?: JwtDecodeOptions): string | JwtPayload | null
token— The JWT string to decode.options— Decode options such ascompletefor full header+payload output.
Returns: The decoded payload, or null if the token cannot be decoded.
Constants
JWT_PRIVATE_KEY
The RSA private key for signing JWTs. Read from the JWT_PRIVATE_KEY
environment variable, or loaded from the PEM file on disk.
Throws at startup if neither source provides a key — running with an empty secret would allow anyone to forge valid JWTs.
const JWT_PRIVATE_KEY: string | Buffer<ArrayBufferLike>
JWT_PUBLIC_KEY
The RSA public key for verifying JWTs. Read from the JWT_PUBLIC_KEY
environment variable; when only JWT_PRIVATE_KEY is set, the matching
public key is DERIVED from it (a disk fallback could not match an
env-provided private key and would make every signed token fail
verification); otherwise loaded from the PEM file on disk.
Throws at startup if no source provides a key.
const JWT_PUBLIC_KEY: string | Buffer<ArrayBufferLike>
JWT_ALGORITHM
The signing algorithm used for JWT operations. Read from the JWT_ALGORITHM
environment variable, defaulting to RS256. An unrecognized value logs an
actionable warning at module load and falls back to RS256 rather than
failing every sign()/verify() call later with an unexplained
"invalid algorithm" error.
const JWT_ALGORITHM: JwtAlgorithm
JWT_EXPIRES_TIME
Token lifetime in seconds. Read from the JWT_EXPIRES_TIME environment
variable, defaulting to 604800 (1 week).
const JWT_EXPIRES_TIME: number
JWT_REFRESH_TIME
Refresh window in seconds — tokens are refreshed if they will expire within
this period. Read from the JWT_REFRESH_TIME environment variable,
defaulting to 3600 (1 hour).
const JWT_REFRESH_TIME: number
Available Providers
| Provider | Package |
|---|---|
| JWT signing keys | @molecule/api-jwt-jsonwebtoken |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1
Runtime Dependencies
@molecule/api-bond
verify is the ONLY way to trust a token — decode does NOT check the
signature. Never make an auth decision from decode(): an attacker can forge any
payload that decode() will happily return. Use verify() (it throws — catch it) for
anything security-relevant; decode() is only for reading a token you do NOT trust.
- The signing key (private key / secret) is SERVER-SIDE only — never ship it to the browser. Only an asymmetric PUBLIC key may be published.
- A JWT payload is READABLE by anyone (base64, not encrypted) — never put a password, secret, or sensitive PII in it.
- Always set + honor expiry (
JWT_EXPIRES_TIME); a non-expiring token can't be revoked. - In a molecule app auth is ALREADY wired: the global
verifyMiddlewareverifies the JWT and populatesres.locals.session, so a handler callsgetUserId(res)— do NOT callverify()/sign()by hand for the session (see theauthskill). Use these directly only for a CUSTOM token, e.g. a signed email/reset link. signis the low-level signer; a token it mints is NOT a session.verifyMiddleware(@molecule/api-resource-user) accepts a JWT only when its payload also carries adeviceIdnaming an ACTIVE row in the app'sdevicestable, so a payload without one is treated as no session:sign({ userId })answers 401 on every route although the token itself verifies. Mint a session withauthorization.set()from@molecule/api-resource-user, which signs and writes the session cookies — the sequence is device → session, and the device call RESOLVES TO THE ID:const deviceId = await get<DeviceService>('device')?.createOrUpdate(userId, deviceName)(theDeviceServicetype comes from@molecule/api-resource-device; do not hand-type aget<{ createOrUpdate(): … }>('device')generic — a wrong one compiles and yieldsundefined), thenauthorization.set(req, res, { userId, deviceId }), as that package's own log-in handler does.authorization.set()throws when the device id is missing rather than mint a token every route answers 401.- Re-signing decoded claims (refresh flows): strip
exp/iatfirst.sign()always setsexpiresIn(defaultJWT_EXPIRES_TIME), and the underlying library throws (Bad "options.expiresIn" option the payload already has an "exp" property) when the payload still carries the oldexp— soconst { exp, iat, ...claims } = verify(oldToken) as JwtPayload; sign(claims)is the correct refresh shape. - Set
JWT_PRIVATE_KEYandJWT_PUBLIC_KEYtogether (or neither). If only the private key is set, the matching public key is DERIVED from it automatically; setting only the public key is for verify-only deployments. - When neither key env var is set, a key pair is auto-generated on disk at
{JWT_KEYS_DIR}/{NODE_ENV}/— defaultJWT_KEYS_DIR:process.cwd() + '/.keys', a stable app-level directory (NOT insidenode_modules, sonpm ci/reinstall never wipes it). SetJWT_KEYS_DIRto relocate it (e.g. a persistent volume in production). A pre-existing pair at the legacynode_modules-relative location is migrated forward automatically (with a logged warning) instead of being silently regenerated. JWT_ALGORITHM(defaultRS256) is validated at module load against theJwtAlgorithmunion; an unrecognized value (e.g. a typo likers256) logs an actionable warning and falls back toRS256instead of failing everysign()/verify()call later with an opaque "invalid algorithm" error.