@nitrotool/jwt 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -0
- package/dist/h3.d.mts +1 -1
- package/dist/h3.d.ts +1 -1
- package/dist/h3.mjs +1 -1
- package/dist/jwt.mjs +5 -4
- package/package.json +2 -2
- package/src/h3.ts +0 -24
- package/src/index.ts +0 -5
- package/src/jwt.ts +0 -80
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @nitrotool/jwt
|
|
2
|
+
|
|
3
|
+
Lightweight JWT utilities for Nitro/UnJS environments with optional h3 helpers.
|
|
4
|
+
|
|
5
|
+
- Built on `@tsndr/cloudflare-worker-jwt`
|
|
6
|
+
- Helpers that read `jwtSecret` from your Nitro runtime config
|
|
7
|
+
- h3 utilities to extract tokens from requests and enforce authentication
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @nitrotool/jwt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer dependency:
|
|
16
|
+
- `h3` is required only when using the h3 helpers.
|
|
17
|
+
|
|
18
|
+
## Importing
|
|
19
|
+
|
|
20
|
+
You can import from the main entry or subpath exports:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Main (all helpers)
|
|
24
|
+
import { encodeJwt, verifyJwt, decodeJwt } from '@nitrotool/jwt';
|
|
25
|
+
|
|
26
|
+
// Subpath (JWT-only)
|
|
27
|
+
import { encodeJwtRaw, verifyJwtRaw, decodeJwtRaw } from '@nitrotool/jwt/jwt';
|
|
28
|
+
|
|
29
|
+
// Subpath (h3 helpers)
|
|
30
|
+
import { extractApiToken, requireApiToken } from '@nitrotool/jwt/h3';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { encodeJwt, verifyJwt, decodeJwt } from '@nitrotool/jwt';
|
|
37
|
+
|
|
38
|
+
type MyClaims = { userId: string };
|
|
39
|
+
|
|
40
|
+
const token = await encodeJwt<MyClaims>({ userId: '123' });
|
|
41
|
+
// Later…
|
|
42
|
+
const isValid = await verifyJwt(token);
|
|
43
|
+
const payload = await decodeJwt<MyClaims>(token); // { userId: '123', exp: ... }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
By default, non-`Raw` helpers read the secret from your runtime config:
|
|
47
|
+
- `useRuntimeConfig().jwtSecret`
|
|
48
|
+
|
|
49
|
+
If you need to pass a secret explicitly, use the `*Raw` variants.
|
|
50
|
+
|
|
51
|
+
## Usage with h3
|
|
52
|
+
|
|
53
|
+
Extract API tokens and enforce authentication in request handlers:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { defineEventHandler } from 'h3';
|
|
57
|
+
import { extractApiToken, requireApiToken } from '@nitrotool/jwt/h3';
|
|
58
|
+
import { decodeJwt } from '@nitrotool/jwt';
|
|
59
|
+
|
|
60
|
+
export default defineEventHandler(async (event) => {
|
|
61
|
+
// Try to read token (Authorization: Bearer <token> or ?token=<token>)
|
|
62
|
+
const token = extractApiToken(event);
|
|
63
|
+
|
|
64
|
+
// Or strictly require it (throws UnauthenticatedError if missing)
|
|
65
|
+
const requiredToken = requireApiToken(event);
|
|
66
|
+
|
|
67
|
+
// Optionally decode/verify
|
|
68
|
+
const claims = await decodeJwt<{ userId: string }>(requiredToken);
|
|
69
|
+
|
|
70
|
+
return { ok: true, userId: claims.userId };
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Supported token locations:
|
|
75
|
+
- Authorization header: `Authorization: Bearer <token>`
|
|
76
|
+
- Query string: `?token=<token>`
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
When using non-`Raw` helpers, ensure a secret is available at runtime:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// Example: Nuxt/Nitro runtime config
|
|
84
|
+
export default defineNuxtConfig({
|
|
85
|
+
runtimeConfig: {
|
|
86
|
+
jwtSecret: process.env.JWT_SECRET || 'super-secret',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Examples
|
|
92
|
+
|
|
93
|
+
Sign with custom TTL:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { encodeJwtRaw } from '@nitrotool/jwt';
|
|
97
|
+
|
|
98
|
+
const token = await encodeJwtRaw(
|
|
99
|
+
{ userId: '123', role: 'admin' },
|
|
100
|
+
process.env.JWT_SECRET!,
|
|
101
|
+
60 * 10 // 10 minutes
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Decode without verifying signature (use only for non-sensitive scenarios):
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { decodeJwt } from '@nitrotool/jwt';
|
|
109
|
+
|
|
110
|
+
const payload = await decodeJwt<{ userId: string }>(token, { verify: false });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Verify with an explicit secret:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { verifyJwtRaw } from '@nitrotool/jwt';
|
|
117
|
+
|
|
118
|
+
const ok = await verifyJwtRaw(token, process.env.JWT_SECRET!);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Errors
|
|
122
|
+
|
|
123
|
+
- `UnauthorizedError('Invalid JWT token.')` is thrown when verification fails in `decodeJwt` / `decodeJwtRaw`.
|
|
124
|
+
- `UnauthenticatedError()` is thrown by `requireApiToken` when no token is present.
|
|
125
|
+
|
|
126
|
+
## Security Notes
|
|
127
|
+
|
|
128
|
+
- Keep `jwtSecret` strong and private.
|
|
129
|
+
- Prefer short TTLs and refresh flows.
|
|
130
|
+
- Only set `verify: false` for non-sensitive, debug-like operations.
|
|
131
|
+
- Rotate secrets periodically and invalidate old tokens if needed.
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
All helpers are asynchronous.
|
|
136
|
+
|
|
137
|
+
### JWT helpers
|
|
138
|
+
|
|
139
|
+
- `encodeJwtRaw<T>(payload, secret, ttl = 60): Promise<string>`
|
|
140
|
+
- Signs a token with the provided `secret`.
|
|
141
|
+
- `ttl` is in seconds. Default: `60`.
|
|
142
|
+
- `exp` is set automatically from `ttl`.
|
|
143
|
+
|
|
144
|
+
- `encodeJwt<T>(payload): Promise<string>`
|
|
145
|
+
- Same as `encodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
|
|
146
|
+
|
|
147
|
+
- `verifyJwtRaw(token, secret): Promise<boolean>`
|
|
148
|
+
- Verifies signature and expiry using the provided `secret`.
|
|
149
|
+
|
|
150
|
+
- `verifyJwt(token): Promise<boolean>`
|
|
151
|
+
- Same as `verifyJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
|
|
152
|
+
|
|
153
|
+
- `decodeJwtRaw<T>(token, secret, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
|
|
154
|
+
- Decodes the token. When `verify` is `true`, verifies signature and expiry.
|
|
155
|
+
- Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.
|
|
156
|
+
- Throws if `verify` is `true` but `secret` is empty.
|
|
157
|
+
|
|
158
|
+
- `decodeJwt<T>(token, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
|
|
159
|
+
- Same as `decodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
|
|
160
|
+
- Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.
|
|
161
|
+
|
|
162
|
+
Types:
|
|
163
|
+
- `ExtendableJwtPayload<T>` lets you define custom claims merged with standard JWT claims.
|
|
164
|
+
|
|
165
|
+
### h3 helpers
|
|
166
|
+
|
|
167
|
+
- `extractBearerToken(event): string | undefined`
|
|
168
|
+
- Reads `Authorization` header and returns the token without `Bearer `.
|
|
169
|
+
|
|
170
|
+
- `extractQueryToken(event): string | undefined`
|
|
171
|
+
- Reads `token` from the query string.
|
|
172
|
+
|
|
173
|
+
- `extractApiToken(event): string | undefined`
|
|
174
|
+
- Returns the first non-empty token found by `extractBearerToken` or `extractQueryToken`.
|
|
175
|
+
|
|
176
|
+
- `requireApiToken(event): string`
|
|
177
|
+
- Same as `extractApiToken`, but throws `UnauthenticatedError` if missing.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/dist/h3.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { H3Event } from 'h3';
|
|
2
2
|
|
|
3
3
|
declare const extractBearerToken: (event: H3Event) => string;
|
|
4
|
-
declare const extractQueryToken: (event: H3Event) => string | undefined;
|
|
4
|
+
declare const extractQueryToken: (event: H3Event, key?: string) => string | undefined;
|
|
5
5
|
declare const extractApiToken: (event: H3Event) => string | undefined;
|
|
6
6
|
declare const requireApiToken: (event: H3Event) => string;
|
|
7
7
|
|
package/dist/h3.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { H3Event } from 'h3';
|
|
2
2
|
|
|
3
3
|
declare const extractBearerToken: (event: H3Event) => string;
|
|
4
|
-
declare const extractQueryToken: (event: H3Event) => string | undefined;
|
|
4
|
+
declare const extractQueryToken: (event: H3Event, key?: string) => string | undefined;
|
|
5
5
|
declare const extractApiToken: (event: H3Event) => string | undefined;
|
|
6
6
|
declare const requireApiToken: (event: H3Event) => string;
|
|
7
7
|
|
package/dist/h3.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { getRequestHeader, getQuery } from 'h3';
|
|
|
2
2
|
import { UnauthenticatedError } from '@nitrotool/errors';
|
|
3
3
|
|
|
4
4
|
const extractBearerToken = (event) => getRequestHeader(event, "Authorization")?.replace("Bearer ", "") || void 0;
|
|
5
|
-
const extractQueryToken = (event) => getQuery(event)?.
|
|
5
|
+
const extractQueryToken = (event, key = "token") => getQuery(event)?.[key] || void 0;
|
|
6
6
|
const extractApiToken = (event) => extractBearerToken(event) || extractQueryToken(event);
|
|
7
7
|
const requireApiToken = (event) => {
|
|
8
8
|
const token = extractApiToken(event);
|
package/dist/jwt.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import jwt from '@tsndr/cloudflare-worker-jwt';
|
|
2
|
-
import {
|
|
2
|
+
import { UnauthorizedError } from '@nitrotool/errors';
|
|
3
3
|
|
|
4
4
|
const encodeJwtRaw = async (payload, secret, ttl = 60) => {
|
|
5
5
|
return await jwt.sign(
|
|
@@ -33,16 +33,17 @@ const verifyJwt = async (token) => {
|
|
|
33
33
|
return verifyJwtRaw(token, secret);
|
|
34
34
|
};
|
|
35
35
|
const decodeJwtRaw = async (token, secret, { verify } = { verify: true }) => {
|
|
36
|
-
if (!secret && verify)
|
|
36
|
+
if (!secret && verify)
|
|
37
|
+
throw new Error("Cannot check signature without secret.");
|
|
37
38
|
if (secret && verify && !await verifyJwtRaw(token, secret)) {
|
|
38
|
-
throw
|
|
39
|
+
throw UnauthorizedError("Invalid JWT token.");
|
|
39
40
|
}
|
|
40
41
|
const { payload } = jwt.decode(token);
|
|
41
42
|
return payload;
|
|
42
43
|
};
|
|
43
44
|
const decodeJwt = async (token, { verify } = { verify: true }) => {
|
|
44
45
|
if (verify && !await verifyJwt(token)) {
|
|
45
|
-
throw
|
|
46
|
+
throw UnauthorizedError("Invalid JWT token.");
|
|
46
47
|
}
|
|
47
48
|
const { payload } = jwt.decode(token);
|
|
48
49
|
return payload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitrotool/jwt",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"main": "dist/index.mjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@tsndr/cloudflare-worker-jwt": "^3.2.0",
|
|
14
|
-
"@nitrotool/errors": "0.0.
|
|
14
|
+
"@nitrotool/errors": "0.0.8"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"h3": "^1.15.3"
|
package/src/h3.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type H3Event,
|
|
3
|
-
getRequestHeader,
|
|
4
|
-
getQuery,
|
|
5
|
-
} from 'h3';
|
|
6
|
-
import {UnauthenticatedError} from "@nitrotool/errors";
|
|
7
|
-
|
|
8
|
-
export const extractBearerToken = (event: H3Event) =>
|
|
9
|
-
getRequestHeader(event, 'Authorization')?.replace('Bearer ', '') || undefined;
|
|
10
|
-
|
|
11
|
-
export const extractQueryToken = (event: H3Event): string | undefined =>
|
|
12
|
-
getQuery<{ token: string }>(event)?.token || undefined;
|
|
13
|
-
|
|
14
|
-
export const extractApiToken = (event: H3Event): string | undefined =>
|
|
15
|
-
extractBearerToken(event) || extractQueryToken(event);
|
|
16
|
-
|
|
17
|
-
export const requireApiToken = (event: H3Event) => {
|
|
18
|
-
const token = extractApiToken(event);
|
|
19
|
-
if (!token) {
|
|
20
|
-
throw UnauthenticatedError();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return token;
|
|
24
|
-
}
|
package/src/index.ts
DELETED
package/src/jwt.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import jwt, {JwtPayload} from '@tsndr/cloudflare-worker-jwt';
|
|
2
|
-
|
|
3
|
-
import { createError } from 'h3';
|
|
4
|
-
export type ExtendableJwtPayload<T extends Record<string, any> = {}> =
|
|
5
|
-
Partial<JwtPayload> & T;
|
|
6
|
-
|
|
7
|
-
export const encodeJwtRaw = async <
|
|
8
|
-
T extends Record<string, any> = {},
|
|
9
|
-
>(
|
|
10
|
-
payload: ExtendableJwtPayload<T>,
|
|
11
|
-
secret: string,
|
|
12
|
-
ttl = 60,
|
|
13
|
-
): Promise<string> => {
|
|
14
|
-
return await jwt.sign(
|
|
15
|
-
{
|
|
16
|
-
exp: Date.now() / 1000 + ttl,
|
|
17
|
-
...payload,
|
|
18
|
-
},
|
|
19
|
-
secret,
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const encodeJwt = async <
|
|
24
|
-
T extends Record<string, any> = {},
|
|
25
|
-
>(
|
|
26
|
-
payload: ExtendableJwtPayload<T>,
|
|
27
|
-
): Promise<string> => {
|
|
28
|
-
const secret =
|
|
29
|
-
//@ts-expect-error Expected.
|
|
30
|
-
typeof useRuntimeConfig === 'function' ? useRuntimeConfig()?.jwtSecret : '';
|
|
31
|
-
return encodeJwtRaw(payload, secret);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const verifyJwtRaw = async (
|
|
35
|
-
token: string,
|
|
36
|
-
secret: string,
|
|
37
|
-
): Promise<boolean> => {
|
|
38
|
-
return new Promise(async (resolve) => {
|
|
39
|
-
if (await jwt.verify(token, secret, {throwError: false})) {
|
|
40
|
-
return resolve(true);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return resolve(false);
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const verifyJwt = async (token: string): Promise<boolean> => {
|
|
48
|
-
const secret =
|
|
49
|
-
//@ts-expect-error Expected.
|
|
50
|
-
typeof useRuntimeConfig === 'function' ? useRuntimeConfig()?.jwtSecret : '';
|
|
51
|
-
return verifyJwtRaw(token, secret);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const decodeJwtRaw = async <
|
|
55
|
-
T extends Record<string, any> = {},
|
|
56
|
-
>(
|
|
57
|
-
token: string,
|
|
58
|
-
secret: string,
|
|
59
|
-
{ verify }: { verify?: boolean } = { verify: true },
|
|
60
|
-
): Promise<ExtendableJwtPayload<T>> => {
|
|
61
|
-
if (!secret && verify) throw new Error('Cannot check signature without secret.');
|
|
62
|
-
if (secret && verify && !(await verifyJwtRaw(token, secret))) {
|
|
63
|
-
throw createError('Invalid JWT token.');
|
|
64
|
-
}
|
|
65
|
-
const { payload } = jwt.decode<ExtendableJwtPayload<T>>(token);
|
|
66
|
-
return payload;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export const decodeJwt = async <
|
|
70
|
-
T extends Record<string, any> = {},
|
|
71
|
-
>(
|
|
72
|
-
token: string,
|
|
73
|
-
{ verify }: { verify?: boolean } = { verify: true },
|
|
74
|
-
): Promise<ExtendableJwtPayload<T>> => {
|
|
75
|
-
if (verify && !(await verifyJwt(token))) {
|
|
76
|
-
throw createError('Invalid JWT token.');
|
|
77
|
-
}
|
|
78
|
-
const { payload } = jwt.decode<ExtendableJwtPayload<T>>(token);
|
|
79
|
-
return payload;
|
|
80
|
-
};
|