# Unjwt

> Expert knowledge for working with unjwt — a low-level, zero-dep, JWT library using the Web Crypto API. Use this skill whenever the user is working with JWS, JWE, JWK, or JWT utilities, including signing, verifying, encrypting, decrypting, key management, and session handling. Trigger on any mention of unjwt or related JWT operations. Use when this capability is needed.

- Skill: `tomevault-io/unjwt` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/unjwt`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/unjwt/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/unjwt

---


# unjwt Skill

Low-level JWT library using the Web Crypto API. Zero runtime dependencies for core; optional peer deps for framework adapters.

Implements JWS (RFC 7515), JWE (RFC 7516), and JWK (RFC 7517).

## Quick orientation

> **Skill written against `unjwt@0.7.2`.** APIs are stable within v0.7 but check the [changelog](https://github.com/sandros94/unjwt/releases) if behaviour seems off on a newer version.

The following is a list of reference files:

- `references/jws.md`: `sign()`, `verify()`, `signMulti()`, `verifyMulti()`, `verifyMultiAll()`, `generalToFlattenedJWS()`, `JWSSignOptions`, `JWSVerifyOptions`, `JWSMultiSignOptions`, `JWSMultiVerifyOptions`, `JWSMultiVerifyAllOptions`, `JWSMultiVerifyOutcome`, `JWSGeneralSerialization`, `JWSFlattenedSerialization`, `JWSMultiSigner`
- `references/jwe.md`: `encrypt()`, `decrypt()`, `encryptMulti()`, `decryptMulti()`, `generalToFlattened()`, `JWEEncryptOptions`, `JWEDecryptOptions`, `JWEMultiEncryptOptions`, `JWEMultiDecryptOptions`, `JWEGeneralSerialization`, `JWEFlattenedSerialization`, `JWEMultiRecipient`
- `references/jwk.md`: `generateKey()`, `generateJWK()`, `importKey()`, `exportKey()`, `wrapKey()`, `unwrapKey()`, `deriveSharedSecret()`, PEM import/export (`importPEM`, `exportPEM`; `importFromPEM`, `exportToPEM` are deprecated aliases), PBES2 key derivation (`deriveKeyFromPassword`, `deriveJWKFromPassword`), JWK set utilities (`getJWKsFromSet`, `getJWKFromSet` — deprecated), JWK cache (`configureJWKCache`, `clearJWKCache`, `WeakMapJWKCache`), key lookup types (`JWKLookupFunction`, `JWKLookupFunctionHeader`), all JWK type definitions
- `references/utils.md`: `base64UrlEncode`/`base64UrlDecode`, `base64Encode`/`base64Decode`, `secureRandomBytes`, `concatUint8Arrays`, `textEncoder`/`textDecoder`, type guards (`isJWK`, `isJWKSet`, `isSymmetricJWK`, `isAsymmetricJWK`, `isPrivateJWK`, `isPublicJWK`, `isCryptoKey`, `isCryptoKeyPair`, `assertCryptoKey`), `validateJwtClaims`, `inferJWSAllowedAlgorithms`, `inferJWEAllowedAlgorithms`, `computeDurationInSeconds`, `Duration` format (aliased by `ExpiresIn` / `NotBeforeIn` / `MaxTokenAge`), `JWTClaimValidationOptions`
- `references/adapters-h3.md`: H3 session adapters (v1 and v2), `useJWESession`, `useJWSSession`, `SessionManager` interface, `SessionConfigJWE`, `SessionConfigJWS`, lifecycle hooks (`onRead`, `onUpdate`, `onClear`, `onExpire`, `onError`), key lookup hooks (`onUnsealKeyLookup`, `onVerifyKeyLookup`), lower-level functions, cookie chunking (v2), header-based tokens, refresh token pattern
- `references/adapters-elysia.md`: Elysia session adapter, `jwsSession`/`jweSession` plugins, ambient `ctx[contextKey]`, `requireSession` guard macro (derived per `contextKey`), multiple sessions (access JWS + refresh JWE), `createJWSSession`/`createJWESession` lower-level, `SessionConfigJWS`/`SessionConfigJWE`, lifecycle hooks (`context` not `event`), cookie chunking, header tokens, `isolatedDeclarations` plugin typing

## Export Paths

| Path                    | Purpose                                                                                              |
| ----------------------- | ---------------------------------------------------------------------------------------------------- |
| `unjwt`                 | Flat barrel: all public functions and types from `jws`, `jwe`, `jwk`, `utils`                        |
| `unjwt/jws`             | `sign()`, `verify()` (Compact) — `signMulti()`, `verifyMulti()` (General JSON Serialization)         |
| `unjwt/jwe`             | `encrypt()`, `decrypt()` (Compact) — `encryptMulti()`, `decryptMulti()` (General JSON Serialization) |
| `unjwt/jwk`             | Key generation, import/export, wrap/unwrap, PEM conversion, PBES2, cache utils                       |
| `unjwt/utils`           | Base64URL encode/decode, type guards, JWT claim validation, `secureRandomBytes`                      |
| `unjwt/adapters/h3`     | H3 session adapter (aliases h3v1)                                                                    |
| `unjwt/adapters/h3v1`   | H3 v1 session adapter (Nuxt v4, Nitro v2)                                                            |
| `unjwt/adapters/h3v2`   | H3 v2 session adapter (Nuxt v5, Nitro v3)                                                            |
| `unjwt/adapters/elysia` | Elysia session adapter — `jwsSession`/`jweSession` plugins (`>=1.4.0`)                               |

## Quick Start

```ts
// Sign and verify (JWS)
import { sign, verify } from "unjwt/jws";
import { generateJWK } from "unjwt/jwk";

const key = await generateJWK("HS256");
const token = await sign({ sub: "user123" }, key, { expiresIn: "1h" });
const { payload } = await verify(token, key);

// Encrypt and decrypt (JWE)
import { encrypt, decrypt } from "unjwt/jwe";

const jwe = await encrypt({ secret: "data" }, "password");
const { payload } = await decrypt(jwe, "password");

// H3 session
import { useJWESession } from "unjwt/adapters/h3v2";

const session = await useJWESession(event, { key: "secret", maxAge: "7D" });
await session.update({ userId: "123" });
```

## Key Concepts

- **`JOSEPayload`**: the payload type accepted by `sign`/`encrypt` — `string | Uint8Array | Record<string, unknown>`; covers both JWT and generic serializable objects. `JWTClaims` remains the spec-compliant sub-type for typed claim access
- **JWK-first key model**: functions accept JWK objects directly; `importKey()` normalizes CryptoKey/JWK/Uint8Array/string
- **Algorithm inference**: `sign`/`encrypt` infer `alg`/`enc` from JWK properties when not explicitly provided; password strings default to PBES2
- **`dir` (direct encryption)**: pass a `CryptoKey | JWK_oct | Uint8Array` directly as the CEK; `enc` must always be specified
- **Multi-recipient JWE**: `encryptMulti()` emits General JSON Serialization (RFC 7516 §7.2.1). One shared CEK, one ciphertext, per-recipient wraps. `dir` and bare `ECDH-ES` are rejected in multi (throw `ERR_JWE_ALG_FORBIDDEN_IN_MULTI`). `decryptMulti()` accepts General + Flattened (auto-normalized); compact tokens stay with `decrypt()`
- **Multi-signature JWS**: `signMulti()` emits General JSON Serialization (RFC 7515 §7.2.1). Shared payload, per-signer protected header. `b64: false` (RFC 7797) must be consistent across all signers. `verifyMulti()` returns the first valid signature; `verifyMultiAll()` returns per-signer outcomes for policy-driven verification (all-must-verify, M-of-N quorum, named signers). Both accept General + Flattened (auto-normalized); compact tokens stay with `verify()`
- **ExpiresIn**: time durations accept numbers (seconds) or strings: `"30s"`, `"10m"`, `"2h"`, `"7D"`, `"1W"`, `"3M"`, `"1Y"` (also long forms: `"minutes"`, `"hours"`, `"days"`, `"weeks"`, `"months"`, `"years"`)
- **H3 Session Adapters**: store JWTs in chunked cookies; sessions are lazy (`id` is `undefined` until `update()` is called)
- **PBES2 security**: default `p2c` (iteration count) is `600_000` per OWASP cookbook; only lower it for legacy interoperability

---
> Source: [sandros94/unjwt](https://github.com/sandros94/unjwt) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-29 -->

