npm package: @nitrotool/jwt
Description: Lightweight JWT utilities for Nuxt/Nitro/UnJS environments **with built-in h3 helpers**.
Latest version: 1.0.5

# @nitrotool/jwt

Lightweight JWT utilities for Nuxt/Nitro/UnJS environments **with built-in h3 helpers**.

- Built on `@tsndr/cloudflare-worker-jwt`
- Helpers that read `jwtSecret` from your Nitro runtime config
- Batteries included with h3 utilities to extract tokens from requests and enforce authentication

Learn more about JWT and how it works:

- [What is a JWT?](https://www.jwt.io/introduction#what-is-json-web-token)
- [JWT.io](https://jwt.io/)

## Installation

```bash
pnpm install @nitrotool/jwt
```

```bash
npm install @nitrotool/jwt
```

Required peer dependency:

- `h3` (this package expects `h3` to be present)

## Nuxt setup

```ts
export default defineNuxtConfig({
    modules: ['@nitrotool/jwt'],
    //You can also 
    jwt: {
        //default secret (only use this locally, and override it in prod)
        secret: 'some-random-secret-yes-it-is-not-very-secure',
        // Alternatively also available as NUXT_JWT_SECRET env var
    },
});

```

### Usage with Nuxt 3/4

> This is available all places where auto-imported can be used on the server-side.

```ts
type MyClaims = { tenantId: string };

const token = await encodeJwt<MyClaims>({tenantId: '123', sub: '123'});

// Later…
const isValid = await verifyJwt(token);
const payload = await decodeJwt<MyClaims>(token);


// or
export default defineEventHandler(async (event) => {
    const jwt = await requireJwt(event);
    // you now have access to a required JWT token via Authorization bearer header.
})

```

## Importing

Import from the main entry or subpaths:

```ts
// Node-only helpers
import {encodeJwtRaw, verifyJwtRaw, decodeJwtRaw} from '@nitrotool/jwt/core';

// h3-only helpers
import {extractApiToken, requireApiToken} from '@nitrotool/jwt/h3';
```

## Quick Start Nitro

### 1. Create plugin to load secret.

```ts
// plugins/0.jwt.ts
import { defineNitroPlugin, useRuntimeConfig } from 'nitropack/runtime';
import { configureJwtRuntime } from '@nitrotool/jwt';

export default defineNitroPlugin(() => {
    configureJwtRuntime(() => useRuntimeConfig() as any);
});

```

### 2. Basic usage

```ts
import {encodeJwt, verifyJwt, decodeJwt} from '@nitrotool/jwt';

type MyClaims = { tenantId: string };

const token = await encodeJwt<MyClaims>({tenantId: '123', sub: '123'});

// Later…
const isValid = await verifyJwt(token);
const payload = await decodeJwt<MyClaims>(token); // { sub: '123', tenantId: '123', exp: ... }
```

By default, non-`Raw` helpers read the secret from your runtime config:

- `useRuntimeConfig().jwtSecret`

If you need to pass a secret explicitly, use the `*Raw` variants.

### 3. In a route handler

Extract API tokens and enforce authentication in request handlers:

#### Require a JWT token
```ts
import {defineEventHandler} from 'h3';
import {requireJwt} from '@nitrotool/jwt';

export default defineEventHandler(async (event) => {
    const decodedJwt = await requireJwt(event)
})
```

#### Extract a JWT token manually
```ts
import { defineEventHandler } from 'h3';
import { extractApiToken, requireApiToken, decodeJwt } from '@nitrotool/jwt';

export default defineEventHandler(async (event) => {
    // Try to read token (Authorization: Bearer <token> or ?<queryKey>=<token>)
    const token = extractApiToken(event, {queryKey: 'token'}); // queryKey defaults to 'token'

    // Or strictly require it (throws UnauthenticatedError if missing)
    const requiredToken = requireApiToken(event);

    // Optionally decode/verify
    const claims = await decodeJwt<{ userId: string }>(requiredToken);

    return { ok: true, userId: claims.userId };
});
```

Supported token locations:

- Authorization header: `Authorization: Bearer <token>`
- Query string: `?token=<token>`

## Configuration

When using non-`Raw` helpers, ensure a secret is available at runtime:

```ts
// nuxt.config.ts (Nuxt/Nitro runtime config)
export default defineNuxtConfig({
    runtimeConfig: {
        jwtSecret: 'some-random-secret-yes-it-is-not-very-secure',
    },
});
```

## Examples

Sign with custom TTL:

```ts
import {encodeJwtRaw} from '@nitrotool/jwt';

const token = await encodeJwtRaw(
    {userId: '123', role: 'admin'},
    process.env.JWT_SECRET!,
    60 * 10 // 10 minutes
);
```

Decode without verifying signature (use only for non-sensitive scenarios):

```ts
import {decodeJwt} from '@nitrotool/jwt';

const payload = await decodeJwt<{ userId: string }>(token, {verify: false});
```

Verify with an explicit secret:

```ts
import {verifyJwtRaw} from '@nitrotool/jwt';

const ok = await verifyJwtRaw(token, process.env.JWT_SECRET!);
```

## Errors

- `UnauthorizedError('Invalid JWT token.')` is thrown when verification fails in `decodeJwt` / `decodeJwtRaw`.
- `UnauthenticatedError()` is thrown by `requireApiToken` when no token is present.

## Security Notes

- Keep `jwtSecret` strong and private.
- Prefer short TTLs and refresh flows.
- Only set `verify: false` for non-sensitive, debug-like operations.
- Rotate secrets periodically and invalidate old tokens if needed.

## API Reference

All helpers are asynchronous.

### JWT helpers

- `encodeJwtRaw<T>(payload, secret, ttl = 60): Promise<string>`
    - Signs a token with the provided `secret`.
    - `ttl` is in seconds. Default: `60`.
    - `exp` is set **automagically** from `ttl`.

- `encodeJwt<T>(payload, ttl = 60): Promise<string>`
    - Same as `encodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.

- `verifyJwtRaw(token, secret): Promise<boolean>`
    - Verifies signature and expiry using the provided `secret`.

- `verifyJwt(token): Promise<boolean>`
    - Same as `verifyJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.

- `decodeJwtRaw<T>(token, secret, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
    - Decodes the token. When `verify` is `true`, verifies signature and expiry.
    - Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.
    - Throws if `verify` is `true` but `secret` is empty.

- `decodeJwt<T>(token, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
    - Same as `decodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
    - Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.

Types:

- `ExtendableJwtPayload<T>` lets you define custom claims merged with standard JWT claims.

### h3 helpers (required)

- `extractBearerToken(event): string | undefined`
    - Reads `Authorization` header and returns the token without `Bearer `.

- `extractQueryToken(event, key = 'token'): string | undefined`
    - Reads `token` from the query string.

- `extractApiToken(event): string | undefined`
    - Returns the first non-empty token found by `extractBearerToken` or `extractQueryToken`.

- `requireApiToken(event): string`
    - Same as `extractApiToken`, but throws `UnauthenticatedError` if missing.

## License

MIT