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 by- sign.
- Returns: Promise<boolean>
Decrypts and validates the token, returning the original data on success.
jwt.decode(token)- token- <string>: Token string previously produced by- sign.
- 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.