WebCrypto-based Iron Implementation
iron-webcrypto is a JavaScript/TypeScript library providing a WebCrypto API-based implementation of `@hapi/iron`. It enables sealing (encrypting and signing) and unsealing JSON-like data using symmetric key encryption with built-in message integrity verification, producing compact, URL-safe string tokens. The current stable version is 2.0.0, which notably dropped support for Node.js versions older than 20 and transitioned to an ESM-only module. It differentiates itself by relying solely on standard WebCrypto APIs, making it highly portable across modern JavaScript runtimes like Node.js v20+, Deno, Bun, and Cloudflare Workers, without depending on Node.js-specific `node:crypto` or `node:buffer` modules. The library's API is designed to be compatible with `@hapi/iron`, facilitating migrations or consistent usage patterns across projects. It ships with full TypeScript type definitions and is primarily intended for server-side or worker environments due to security implications of client-side secret exposure, which could expose encryption secrets.
Common errors
-
ReferenceError: require is not defined in ES module scope
cause Attempting to use CommonJS `require()` syntax with an ESM-only package.fixChange `const Iron = require('iron-webcrypto')` to `import * as Iron from 'iron-webcrypto'` and ensure your project is configured for ESM. -
TypeError: crypto.subtle is undefined
cause Running `iron-webcrypto` in an environment that lacks the WebCrypto API, most commonly an older Node.js version (<v20).fixUpgrade your Node.js version to v20 or higher. Alternatively, ensure your environment provides a `crypto.subtle` implementation. -
RangeError: Password must be at least 32 characters long.
cause The provided password for `seal` or `unseal` is shorter than the minimum required length (default 32 characters).fixProvide a password that meets or exceeds the minimum length configured in `SealOptions.encryption.minPasswordlength` (default: 32 characters). Always use strong, securely generated passwords.
Warnings
- breaking Version 2.0.0 of `iron-webcrypto` drops support for Node.js versions older than v20. Running on unsupported versions may lead to runtime errors or unexpected behavior due to missing WebCrypto APIs.
- breaking Internal utility functions for base64/buffer operations, which were not part of the public API, have been removed in v2.0.0. If you were using these, migrate to dedicated libraries like `uint8array-extras`.
- gotcha While `iron-webcrypto` technically functions in a browser environment due to its WebCrypto reliance, it is not recommended for client-side use. Exposing encryption secrets (passwords) to the client poses significant security risks.
- gotcha Encryption secrets (passwords) should never be hardcoded in application code. They must be stored securely, ideally in environment variables, a secrets manager, or a secure configuration system.
- gotcha The default `minPasswordLength` is 32 characters. Using passwords shorter than this may be insecure and could result in runtime errors or weakened cryptography.
Install
-
npm install iron-webcrypto -
yarn add iron-webcrypto -
pnpm add iron-webcrypto
Imports
- Iron
const Iron = require('iron-webcrypto')import * as Iron from 'iron-webcrypto'
- seal, unseal
import { seal } from 'iron-webcrypto/lib/seal'import { seal, unseal } from 'iron-webcrypto' - defaults
import { defaults } from 'iron-webcrypto'
Quickstart
import * as Iron from 'iron-webcrypto'
async function runCryptoExample() {
// It's crucial to use a strong, randomly generated secret stored securely
// in environment variables or a secrets manager. NEVER hardcode in production.
const password = process.env.IRON_SECRET_KEY ?? 'a_long_random_secret_please_change_me_32_chars_min'
if (password.length < 32) {
console.error('Warning: Password must be at least 32 characters long for security.')
return
}
const payload = { userId: 123, scope: ['user'], issuedAt: Date.now() }
try {
const sealed = await Iron.seal(payload, password, Iron.defaults)
console.log('Sealed token:', sealed)
// Simulate receiving the token later or in another service
const unsealed = await Iron.unseal(sealed, password, Iron.defaults)
console.log('Unsealed payload:', unsealed)
// Example with TTL (Time To Live)
const optionsWithTTL = { ...Iron.defaults, ttl: 5000 } // 5 seconds expiry
const sealedWithTTL = await Iron.seal(payload, password, optionsWithTTL)
console.log('Sealed token with TTL:', sealedWithTTL)
// Wait for expiry (optional, for demonstration)
// await new Promise(resolve => setTimeout(resolve, 6000))
// try {
// await Iron.unseal(sealedWithTTL, password, optionsWithTTL)
// } catch (e) {
// console.log('Expected expiry error:', e.message)
// }
} catch (error) {
console.error('Cryptography operation failed:', error)
}
}
runCryptoExample()