Protect credentials with confidential tokens ensuring integrity and privacy.
deno add jsr:@neabyte/secure-tokenimport JWT from '@neabyte/secure-token'
const jwt = new JWT({
secret: 'super-secret',
version: '1.0.0',
expireIn: '1h'
})
const token = await jwt.sign({ userId: '123', role: 'admin' })
const isValid = await jwt.verify(token)
const payload = await jwt.decode(token)const options = {
algorithm: 'aes-128-gcm', // optional (default: 'aes-128-gcm')
secret: 'super-secret', // required (secret used to derive the encryption key)
issuer: 'secure-token', // optional (default: 'secure-token')
version: '1.0.0', // required (application token version)
expireIn: '1h' // required (e.g., '30m', '7d', '500ms')
}const payload = { userId: 123, role: 'admin' }
const token = await jwt.sign(payload)
console.log(token) // <base64-encoded-token>const isValid = await jwt.verify(token)
console.log(isValid) // true | falseconst payload = await jwt.decode(token)
console.log(payload) // { userId: 123, role: 'admin' }sign accepts any JSON-serializable value (not just objects). null and undefined are rejected.
await jwt.sign('user-123') // string
await jwt.sign(42) // number
await jwt.sign(true) // boolean
await jwt.sign(['a', 'b', 'c']) // array
await jwt.sign({ a: 1, b: 2 }) // object
// Decoding returns the original value
const token = await jwt.sign(['a', 1, false])
const data = await jwt.decode(token)
// data => ['a', 1, false]Creates a JWT instance with validated options and parsed expiration.
new JWT(options)options<JWTOptions>: Configuration for the instance. See Configuration → JWT Options.algorithm<'aes-128-gcm' | 'aes-256-gcm'>: (Optional) Default:"aes-128-gcm".secret<string>: Secret used to derive the encryption key.issuer<string>: (Optional) Issuer bound via AAD. Default:"secure-token".version<string>: Application token version to bind and validate.expireIn<string>: Expiration duration like"1h","30m","7d"(max 1 year).
- Returns:
JWT
Signs data into a token using AES-GCM with issuer-version AAD and Base64-encodes the result.
jwt.sign(data)data<unknown>: Arbitrary JSON-serializable data to embed in the token payload.- Returns:
Promise<string>
Validates structure, expiration, version binding, and decryptability. Returns true when valid.
jwt.verify(token)token<string>: Token string previously produced bysign.- Returns:
Promise<boolean>
Decrypts and validates the token, returning the original data on success.
jwt.decode(token)token<string>: Token string previously produced bysign.- Returns:
Promise<unknown>
Important
All operations throw on failure (e.g., invalid format, version mismatch, expired token, or decryption error). Wrap calls in try/catch and handle errors explicitly, and surface meaningful error messages.
try {
const token = await jwt.sign({ userId: '123' })
const isValid = await jwt.verify(token)
} catch (error) {
// handle or log the error message
}-
Sign
- Validate input → build payload
{ data, iat, exp, version } - Encrypt payload JSON with AES-GCM using AAD:
issuer-version - Assemble
TokenData{ encrypted, iv, tag, iat, exp, version } - Return
btoa(JSON.stringify(TokenData))
- Validate input → build payload
-
Decode
- Validate token string →
atob→ parseTokenData - Check
exp, ensureversionmatches instance - Decrypt with AES-GCM using AAD:
issuer-version→ parse payload - Validate payload, check timestamps/version match outer token
- Return
payload.data
- Validate token string →
-
Verify
- Calls
decode(token)and returnstrueif it succeeds, elsefalse
- Calls
- AES-GCM provides confidentiality and integrity (auth tag)
- AAD binds tokens to
issuerandversion(context-bound tokens) - Expiration enforced and capped (max 1 year)
- Outer and inner
{ iat, exp, version }must match
The final token is a Base64 string of a JSON object:
{
"encrypted": "<hex>",
"iv": "<hex>",
"tag": "<hex>",
"exp": 1735689600,
"iat": 1735686000,
"version": "1.0.0"
}The inner payload { data, iat, exp, version } is JSON-encoded, then AES-GCM encrypted to produce the hex fields above, ensuring integrity, authenticity, and issuer-version binding enforcement throughout.
- Requires WebCrypto (
crypto.subtle), available in Deno ≥ the version shown in the badge. - Works in Deno without additional flags. No Node polyfills included.
- Salt Secrets with a Strong Random Generator
- Token Reissue During Rotation Window
- Refresh Token Pattern (Rolling Expiration)
- Multi-Issuer Setup (Microservices)
- Keep secrets out of source control. Use secret managers; never commit or log secrets.
- Use strong, unique secrets per environment. Don't reuse secrets between staging, production, or projects.
- Ensure time synchronization. JWT requires accurate clocks. Enable NTP or secure time on all nodes.
- Invalid time format: Ensure
expireInfollows^(\\d+)(ms|s|m|h|d|M|y)$. - Version mismatch: The instance
versionmust match the token’s embedded version. - Token expired: Increase
expireInor renew the token. - Invalid token format/structure: Ensure you pass the exact string returned by
signand avoid accidental whitespace/encoding changes and mismatched base64 or JSON corruption. - Wrong issuer/secret/algorithm: Tokens are bound to
issuerandversion; changing any ofissuer,version,secret, oralgorithmafter issuing will invalidate existing tokens.
deno task testContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT license. See the LICENSE file for more info.