next-token-auth 1.0.15 → 1.0.16

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.
Files changed (2) hide show
  1. package/README.md +528 -317
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,55 +1,41 @@
1
1
  # next-token-auth
2
2
 
3
- A production-grade authentication library for Next.js. Handles access tokens, refresh tokens, session management, and route protection — so you don't have to wire it all up yourself.
3
+ A production-grade authentication library for Next.js that handles the hard parts of auth so you can focus on building features.
4
4
 
5
- Works with both the App Router and Pages Router. Fully typed with TypeScript.
5
+ Works with both App Router and Pages Router. Fully typed with TypeScript.
6
6
 
7
- > **Breaking change in v1.1.0:** The secret is now server-side only. You must split your config into `AuthConfig` (server) and `ClientAuthConfig` (client), and mount `createAuthHandlers` at `app/api/auth/[action]/route.ts`. See the Quick Start below.
7
+ > **Breaking change in v1.1.0:** The secret is now server-side only for security. You'll need to split your config and add one Route Handler file. See the Quick Start below — it takes 5 minutes.
8
8
 
9
9
  ---
10
10
 
11
- ## The Problem
11
+ ## Why This Exists
12
12
 
13
- Authentication in Next.js involves a lot of moving parts:
13
+ Authentication in Next.js is tedious. You need to:
14
14
 
15
- - Storing tokens securely
16
- - Refreshing access tokens before they expire
17
- - Keeping client and server sessions in sync
18
- - Protecting routes on both the client and server
19
- - Wiring up login, logout, and user fetching from scratch
15
+ - Store tokens securely
16
+ - Refresh access tokens before they expire
17
+ - Keep client and server sessions in sync
18
+ - Protect routes on both the client and server
19
+ - Handle login, logout, and session restoration
20
+ - Wire up API calls with Bearer tokens
20
21
 
21
- Most projects end up with hundreds of lines of boilerplate before a single feature is built.
22
+ Most projects spend days on auth boilerplate before shipping a single feature.
22
23
 
23
- ---
24
-
25
- ## The Solution
26
-
27
- `next-token-auth` gives you a single `AuthProvider` and a set of hooks that handle the entire auth lifecycle. You configure your API endpoints once, and the library takes care of the rest:
28
-
29
- - Tokens are stored in cookies or memory
30
- - Access tokens are automatically refreshed before they expire
31
- - Sessions are restored on page load from stored tokens
32
- - Routes can be protected client-side with a hook or server-side with middleware
33
- - Every API request made through the built-in fetch wrapper gets a `Bearer` token injected automatically
24
+ `next-token-auth` gives you all of this in a single `AuthProvider` and a few hooks. Configure your API endpoints once, and the library handles the rest.
34
25
 
35
26
  ---
36
27
 
37
- ## Features
38
-
39
- - `AuthProvider` React context provider that initializes and manages auth state
40
- - `useAuth` — login, logout, refresh, and session in one hook
41
- - `useSession` read-only access to the current session
42
- - `useRequireAuth` redirects unauthenticated users, works in App Router and Pages Router
43
- - Token storage in cookies or in-memory
44
- - AES-GCM encrypted session cookies (server-side)
45
- - Automatic access token refresh on a 30-second interval (when `autoRefresh` is enabled)
46
- - 401 refresh retry built into the HTTP client
47
- - `getServerSession` read and validate the session in server components and API routes
48
- - `withAuth` — higher-order function to protect App Router route handlers
49
- - `authMiddleware` — Next.js middleware factory for edge-level route protection with guest-only route support
50
- - Flexible expiry parsing: `"15m"`, `"2h"`, `"2d"`, `"7d"`, `"1w"`, or plain seconds
51
- - Three expiry strategies: `backend`, `config`, `hybrid`
52
- - Fully typed with TypeScript generics for custom user shapes
28
+ ## What You Get
29
+
30
+ - One `<AuthProvider>` wrapper for your entire app
31
+ - Three hooks: `useAuth()`, `useSession()`, `useRequireAuth()`
32
+ - Automatic token refresh before expiry
33
+ - HttpOnly encrypted cookies (tokens never exposed to JavaScript)
34
+ - Server-side session validation via `getServerSession()`
35
+ - Next.js middleware for edge-level route protection
36
+ - Guest-only routes (redirect authenticated users away from login pages)
37
+ - Flexible expiry formats: `"15m"`, `"2h"`, `"2d"`, `"7d"`, or plain seconds
38
+ - Full TypeScript support with generics for your custom user shape
53
39
 
54
40
  ---
55
41
 
@@ -57,30 +43,23 @@ Most projects end up with hundreds of lines of boilerplate before a single featu
57
43
 
58
44
  ```bash
59
45
  npm install next-token-auth
60
- # or
61
- yarn add next-token-auth
62
- # or
63
- pnpm add next-token-auth
64
- # or
65
- bun add next-token-auth
66
46
  ```
67
47
 
68
- **Peer dependencies** (already installed in any Next.js project):
69
-
70
- ```
71
- next >= 13
72
- react >= 18
73
- react-dom >= 18
74
- ```
48
+ Peer dependencies (already in any Next.js project):
49
+ - `next >= 15.5.14`
50
+ - `react >= 18`
51
+ - `react-dom >= 18`
75
52
 
76
53
  ---
77
54
 
78
55
  ## Quick Start
79
56
 
80
- ### 1. Create your server config
57
+ ### Step 1: Create your server config
58
+
59
+ This file contains your `secret` and backend API URL. Never import it in client components.
81
60
 
82
61
  ```ts
83
- // lib/auth.ts (SERVER-SIDE ONLY — never import in client components)
62
+ // lib/auth.ts
84
63
  import type { AuthConfig } from "next-token-auth";
85
64
 
86
65
  interface User {
@@ -90,23 +69,27 @@ interface User {
90
69
  }
91
70
 
92
71
  export const authConfig: AuthConfig<User> = {
93
- baseUrl: process.env.API_URL!, // No NEXT_PUBLIC_ prefix needed
72
+ // Your backend API base URL (no NEXT_PUBLIC_ prefix needed)
73
+ baseUrl: process.env.API_URL!,
94
74
 
75
+ // Your backend auth endpoints
95
76
  endpoints: {
96
- login: "/auth/login",
97
- refresh: "/auth/refresh",
98
- logout: "/auth/logout",
99
- me: "/auth/me",
77
+ login: "/auth/login", // POST { email, password } → returns tokens + user
78
+ refresh: "/auth/refresh", // POST { refreshToken } → returns new tokens
79
+ logout: "/auth/logout", // POST (optional)
80
+ me: "/auth/me", // GET → returns user profile (optional)
100
81
  },
101
82
 
83
+ // Route protection rules
102
84
  routes: {
103
- public: ["/", "/about"],
104
- guestOnly: ["/login", "/register"],
105
- protected: ["/dashboard*"],
106
- loginPath: "/login",
107
- redirectAuthenticatedTo: "/dashboard",
85
+ public: ["/", "/about"], // always accessible
86
+ guestOnly: ["/login", "/register"], // only when NOT logged in
87
+ protected: ["/dashboard*", "/profile*"], // requires auth
88
+ loginPath: "/login", // where to send unauthenticated users
89
+ redirectAuthenticatedTo: "/dashboard", // where to send authenticated users who hit guestOnly routes
108
90
  },
109
91
 
92
+ // Token storage settings
110
93
  token: {
111
94
  storage: "cookie",
112
95
  cookieName: "myapp.session",
@@ -114,33 +97,46 @@ export const authConfig: AuthConfig<User> = {
114
97
  sameSite: "lax",
115
98
  },
116
99
 
117
- secret: process.env.AUTH_SECRET!, // SERVER-SIDE ONLY
100
+ // Encryption secret (32+ random characters)
101
+ secret: process.env.AUTH_SECRET!,
118
102
 
103
+ // Auto-refresh tokens before they expire
119
104
  autoRefresh: true,
120
105
 
106
+ // Token expiry (matches your backend JWT settings)
121
107
  expiry: {
122
- accessTokenExpiresIn: "2d",
108
+ accessTokenExpiresIn: "2d", // can also be a number in seconds
123
109
  refreshTokenExpiresIn: "7d",
124
- strategy: "hybrid",
110
+ strategy: "hybrid", // backend first, fallback to config
125
111
  },
126
112
  };
127
113
  ```
128
114
 
129
- ### 2. Create your client config
115
+ **Important:** Use any route names you want. The library doesn't enforce `/login` or `/dashboard` — everything is driven by your config.
116
+
117
+ ---
118
+
119
+ ### Step 2: Create your client config
120
+
121
+ This is safe to import anywhere, including client components. It doesn't contain secrets.
130
122
 
131
123
  ```ts
132
- // lib/auth.client.ts (safe to import anywhere, including client components)
124
+ // lib/auth.client.ts
133
125
  import type { ClientAuthConfig } from "next-token-auth";
134
126
 
135
127
  export const clientAuthConfig: ClientAuthConfig = {
136
128
  token: {
137
- cookieName: "myapp.session",
129
+ cookieName: "myapp.session", // must match server config
138
130
  },
139
131
  autoRefresh: true,
140
132
  };
141
133
  ```
142
134
 
143
- ### 3. Mount the Route Handlers
135
+ ---
136
+
137
+ ### Step 3: Mount the Route Handlers
138
+
139
+ Create this file to handle login, logout, refresh, and session endpoints automatically.
144
140
 
145
141
  ```ts
146
142
  // app/api/auth/[action]/route.ts
@@ -150,13 +146,17 @@ import { authConfig } from "@/lib/auth";
150
146
  export const { GET, POST } = createAuthHandlers(authConfig);
151
147
  ```
152
148
 
153
- This creates four endpoints automatically:
149
+ This creates four internal endpoints:
154
150
  - `POST /api/auth/login` — authenticates and sets HttpOnly cookie
155
151
  - `POST /api/auth/logout` — clears the session cookie
156
152
  - `POST /api/auth/refresh` — refreshes the access token
157
153
  - `GET /api/auth/session` — returns current user and auth status
158
154
 
159
- ### 4. Wrap your app with `AuthProvider`
155
+ Your `AuthProvider` calls these automatically. You never call them directly.
156
+
157
+ ---
158
+
159
+ ### Step 4: Wrap your app
160
160
 
161
161
  ```tsx
162
162
  // app/layout.tsx
@@ -167,36 +167,63 @@ export default function RootLayout({ children }: { children: React.ReactNode })
167
167
  return (
168
168
  <html lang="en">
169
169
  <body>
170
- <AuthProvider config={clientAuthConfig}>{children}</AuthProvider>
170
+ <AuthProvider config={clientAuthConfig}>
171
+ {children}
172
+ </AuthProvider>
171
173
  </body>
172
174
  </html>
173
175
  );
174
176
  }
175
177
  ```
176
178
 
177
- ### 5. Use the hooks
179
+ ---
180
+
181
+ ### Step 5: Build your login page
178
182
 
179
183
  ```tsx
184
+ // app/login/page.tsx
180
185
  "use client";
181
186
 
182
187
  import { useAuth } from "next-token-auth/react";
188
+ import { useState } from "react";
183
189
 
184
190
  export default function LoginPage() {
185
191
  const { login, isLoading } = useAuth();
192
+ const [email, setEmail] = useState("");
193
+ const [password, setPassword] = useState("");
194
+ const [error, setError] = useState("");
186
195
 
187
- async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
196
+ async function handleSubmit(e: React.FormEvent) {
188
197
  e.preventDefault();
189
- const form = new FormData(e.currentTarget);
190
- await login({ email: form.get("email"), password: form.get("password") });
191
- window.location.href = "/dashboard";
198
+ setError("");
199
+
200
+ try {
201
+ await login({ email, password });
202
+ window.location.href = "/dashboard";
203
+ } catch (err) {
204
+ setError(err instanceof Error ? err.message : "Login failed");
205
+ }
192
206
  }
193
207
 
194
208
  return (
195
209
  <form onSubmit={handleSubmit}>
196
- <input name="email" type="email" required />
197
- <input name="password" type="password" required />
210
+ <input
211
+ type="email"
212
+ value={email}
213
+ onChange={(e) => setEmail(e.target.value)}
214
+ placeholder="Email"
215
+ required
216
+ />
217
+ <input
218
+ type="password"
219
+ value={password}
220
+ onChange={(e) => setPassword(e.target.value)}
221
+ placeholder="Password"
222
+ required
223
+ />
224
+ {error && <p style={{ color: "red" }}>{error}</p>}
198
225
  <button type="submit" disabled={isLoading}>
199
- {isLoading ? "Signing in" : "Sign in"}
226
+ {isLoading ? "Signing in..." : "Sign in"}
200
227
  </button>
201
228
  </form>
202
229
  );
@@ -205,101 +232,89 @@ export default function LoginPage() {
205
232
 
206
233
  ---
207
234
 
208
- ## Usage
235
+ ### Step 6: Protect a page
209
236
 
210
- ### `AuthProvider`
237
+ ```tsx
238
+ // app/dashboard/page.tsx
239
+ "use client";
211
240
 
212
- Wrap your application once at the root. It calls `/api/auth/session` on mount to restore the session from the HttpOnly cookie, then subscribes to session changes.
241
+ import { useRequireAuth, useSession } from "next-token-auth/react";
213
242
 
214
- ```tsx
215
- <AuthProvider config={clientAuthConfig}>
216
- {children}
217
- </AuthProvider>
218
- ```
243
+ export default function DashboardPage() {
244
+ // Redirects to /login if not authenticated
245
+ useRequireAuth();
219
246
 
220
- When `autoRefresh: true` is set, the provider calls `/api/auth/refresh` periodically based on `refreshThreshold`.
247
+ const { user, isAuthenticated } = useSession();
221
248
 
222
- #### Client config reference (`ClientAuthConfig`)
249
+ if (!isAuthenticated) return null; // while redirecting
223
250
 
224
- This is what you pass to `AuthProvider` — it does NOT contain `secret` or `baseUrl`.
251
+ return (
252
+ <main>
253
+ <h1>Dashboard</h1>
254
+ <p>Welcome, {user?.name}</p>
255
+ </main>
256
+ );
257
+ }
258
+ ```
225
259
 
226
- ```ts
227
- interface ClientAuthConfig {
228
- token?: {
229
- cookieName?: string; // default: "next-token-auth.session"
230
- };
260
+ ---
231
261
 
232
- routes?: {
233
- loginPath?: string; // where to redirect unauthenticated users (default: "/login")
234
- redirectAuthenticatedTo?: string; // where to redirect authenticated users on guestOnly routes (default: "/dashboard")
235
- };
262
+ ### Step 7: Add middleware (optional but recommended)
236
263
 
237
- autoRefresh?: boolean; // automatically refresh tokens before expiry
264
+ Protect routes at the edge for better performance and security.
238
265
 
239
- refreshThreshold?: number; // seconds before expiry to trigger refresh (default: 60)
266
+ ```ts
267
+ // middleware.ts (project root, next to app/)
268
+ import { authMiddleware } from "next-token-auth/server";
269
+ import { authConfig } from "@/lib/auth";
240
270
 
241
- // Lifecycle callbacks
242
- onLogin?: (session: AuthSession) => void;
243
- onLogout?: () => void;
244
- }
245
- ```
271
+ export const middleware = authMiddleware(authConfig);
246
272
 
247
- #### Server config reference (`AuthConfig`)
273
+ export const config = {
274
+ // Run middleware on these routes
275
+ matcher: ["/login", "/register", "/dashboard*", "/profile*"],
276
+ };
277
+ ```
248
278
 
249
- This is used in `createAuthHandlers`, `authMiddleware`, `getServerSession`, and `withAuth`. Never import this in a client component.
279
+ **Next.js 16+ users:** Rename the file to `proxy.ts` and export `proxy` instead of `middleware`:
250
280
 
251
281
  ```ts
252
- interface AuthConfig<User = unknown> {
253
- baseUrl: string; // Backend API base URL (no NEXT_PUBLIC_ needed)
282
+ // proxy.ts
283
+ export const proxy = authMiddleware(authConfig);
284
+ ```
254
285
 
255
- endpoints: {
256
- login: string; // required
257
- refresh: string; // required
258
- register?: string;
259
- logout?: string;
260
- me?: string; // fetched to populate session.user
261
- };
286
+ ---
262
287
 
263
- routes?: {
264
- public: string[]; // always accessible
265
- protected: string[]; // require auth, supports wildcard: "/dashboard*"
266
- guestOnly?: string[]; // only accessible when NOT authenticated
267
- loginPath?: string; // where to redirect unauthenticated users (default: "/login")
268
- redirectAuthenticatedTo?: string; // where to redirect authenticated users on guestOnly routes (default: "/dashboard")
269
- };
288
+ ## How It Works
270
289
 
271
- token: {
272
- storage: "cookie" | "memory";
273
- cookieName?: string;
274
- secure?: boolean; // default: true
275
- sameSite?: "strict" | "lax" | "none"; // default: "lax"
276
- };
290
+ ### The Flow
277
291
 
278
- secret: string; // AES-GCM encryption key SERVER-SIDE ONLY
292
+ 1. User submits login form `useAuth().login()` is called
293
+ 2. Client sends credentials to `POST /api/auth/login` (your Route Handler)
294
+ 3. Route Handler calls your backend API, gets tokens back
295
+ 4. Route Handler encrypts tokens with your `secret` and sets an HttpOnly cookie
296
+ 5. Client receives `{ ok: true, user }` (no tokens — they're in the cookie)
297
+ 6. `AuthProvider` updates state → `session.isAuthenticated = true`
298
+ 7. On page reload, `AuthProvider` calls `GET /api/auth/session` to restore the session
299
+ 8. Route Handler decrypts the cookie, validates expiry, fetches user profile, returns `{ user, isAuthenticated }`
279
300
 
280
- autoRefresh?: boolean;
281
- refreshThreshold?: number;
301
+ ### Why HttpOnly Cookies?
282
302
 
283
- expiry?: {
284
- accessTokenExpiresIn?: number | string; // e.g. "2d", 3600
285
- refreshTokenExpiresIn?: number | string; // e.g. "7d"
286
- strategy?: "backend" | "config" | "hybrid"; // default: "hybrid"
287
- };
303
+ Tokens stored in HttpOnly cookies cannot be read by JavaScript, which protects against XSS attacks. The browser automatically sends the cookie with every request to your domain, so you don't need to manually attach tokens.
288
304
 
289
- fetchFn?: typeof fetch;
305
+ The downside: you can't call external APIs directly from the client with the access token. Instead, proxy through your own API routes (see "Making Authenticated API Requests" below).
290
306
 
291
- // Lifecycle callbacks
292
- onLogin?: (session: AuthSession<User>) => void;
293
- onLogout?: () => void;
294
- onRefreshError?: (error: unknown) => void;
295
- }
296
- ```
307
+ ### Why Split the Config?
308
+
309
+ If `secret` is in the client config, it gets bundled into your JavaScript and exposed to the browser. Splitting the config ensures the secret only exists server-side.
297
310
 
298
311
  ---
299
312
 
300
- ### `useAuth`
313
+ ## API Reference
314
+
315
+ ### `useAuth()`
301
316
 
302
- The primary hook. Gives you everything you need to build auth flows.
317
+ The main hook for authentication operations.
303
318
 
304
319
  ```ts
305
320
  const { session, login, logout, refresh, isLoading } = useAuth<User>();
@@ -307,27 +322,37 @@ const { session, login, logout, refresh, isLoading } = useAuth<User>();
307
322
 
308
323
  | Property | Type | Description |
309
324
  |-------------|-----------------------------------------|--------------------------------------------------|
310
- | `session` | `AuthSession<User>` | Current auth session (user + isAuthenticated) |
311
- | `login` | `(input: LoginInput) => Promise<void>` | POST to `/api/auth/login`, sets HttpOnly cookie |
312
- | `logout` | `() => Promise<void>` | POST to `/api/auth/logout`, clears cookie |
313
- | `refresh` | `() => Promise<void>` | POST to `/api/auth/refresh`, updates cookie |
314
- | `isLoading` | `boolean` | `true` while initializing or during login |
325
+ | `session` | `AuthSession<User>` | Current user and auth status |
326
+ | `login` | `(input: LoginInput) => Promise<void>` | Authenticate user, sets HttpOnly cookie |
327
+ | `logout` | `() => Promise<void>` | Clears session, calls backend logout if configured |
328
+ | `refresh` | `() => Promise<void>` | Manually refresh the access token |
329
+ | `isLoading` | `boolean` | `true` during initialization or login |
330
+
331
+ `LoginInput` is flexible — pass any fields your backend expects:
315
332
 
316
- `LoginInput` is an open object (`{ [key: string]: unknown }`), so you can pass any fields your backend expects.
333
+ ```ts
334
+ await login({ email, password });
335
+ await login({ username, password, rememberMe: true });
336
+ ```
317
337
 
318
338
  ---
319
339
 
320
- ### `useSession`
340
+ ### `useSession()`
321
341
 
322
- Read-only access to the current session. Use this in components that only need to display user data.
342
+ Read-only access to the current session. Use this in components that only display user data.
323
343
 
324
344
  ```ts
325
- const { user, tokens, isAuthenticated } = useSession<User>();
345
+ const { user, isAuthenticated } = useSession<User>();
326
346
  ```
327
347
 
348
+ Returns:
349
+ - `user` — your user object (or `null` if not authenticated)
350
+ - `tokens` — always `null` on the client (tokens are HttpOnly)
351
+ - `isAuthenticated` — `true` if the user is logged in
352
+
328
353
  ---
329
354
 
330
- ### `useRequireAuth`
355
+ ### `useRequireAuth(options?)`
331
356
 
332
357
  Redirects unauthenticated users. Call it at the top of any protected client component.
333
358
 
@@ -335,45 +360,49 @@ Redirects unauthenticated users. Call it at the top of any protected client comp
335
360
  useRequireAuth({ redirectTo: "/login" });
336
361
  ```
337
362
 
338
- You can also pass a custom handler instead of a redirect path:
363
+ Options:
364
+
365
+ | Option | Type | Default | Description |
366
+ |---------------------|--------------|------------|--------------------------------------------------|
367
+ | `redirectTo` | `string` | `"/login"` | Where to send unauthenticated users |
368
+ | `onUnauthenticated` | `() => void` | — | Custom handler instead of redirect |
369
+
370
+ The hook waits for `isLoading` to finish before redirecting, so you won't see a flash.
371
+
372
+ Custom redirect example:
339
373
 
340
374
  ```ts
375
+ import { useRouter } from "next/navigation";
376
+
341
377
  useRequireAuth({
342
378
  onUnauthenticated: () => router.push("/login?from=/dashboard"),
343
379
  });
344
380
  ```
345
381
 
346
- The hook waits for `isLoading` to be `false` before acting, so it won't flash a redirect during the initial session restore.
347
-
348
- | Option | Type | Default |
349
- |---------------------|--------------|------------|
350
- | `redirectTo` | `string` | `"/login"` |
351
- | `onUnauthenticated` | `() => void` | — |
352
-
353
382
  ---
354
383
 
355
384
  ### Making Authenticated API Requests
356
385
 
357
- Since tokens are stored in HttpOnly cookies (inaccessible to JavaScript), you cannot manually add `Authorization` headers from the client. Instead, your backend API routes should read the session cookie and extract the access token server-side.
386
+ Since tokens are in HttpOnly cookies (inaccessible to JavaScript), you can't add `Authorization` headers from the client. Instead, proxy through your own API routes.
358
387
 
359
- For client-side requests to your own API:
388
+ **Client-side:**
360
389
 
361
390
  ```ts
362
391
  "use client";
363
392
 
364
- export default function Orders() {
365
- async function fetchOrders() {
393
+ export default function OrdersPage() {
394
+ async function loadOrders() {
366
395
  // The session cookie is automatically sent with this request
367
396
  const res = await fetch("/api/orders");
368
397
  const data = await res.json();
369
398
  console.log(data);
370
399
  }
371
400
 
372
- return <button onClick={fetchOrders}>Load Orders</button>;
401
+ return <button onClick={loadOrders}>Load Orders</button>;
373
402
  }
374
403
  ```
375
404
 
376
- Then in your API route, read the session:
405
+ **Server-side (your API route):**
377
406
 
378
407
  ```ts
379
408
  // app/api/orders/route.ts
@@ -381,7 +410,7 @@ import { getServerSession } from "next-token-auth/server";
381
410
  import { authConfig } from "@/lib/auth";
382
411
  import { cookies } from "next/headers";
383
412
 
384
- export async function GET(req: Request) {
413
+ export async function GET() {
385
414
  const cookieStore = await cookies();
386
415
  const session = await getServerSession(
387
416
  { cookies: { get: (name) => cookieStore.get(name) } },
@@ -392,9 +421,11 @@ export async function GET(req: Request) {
392
421
  return Response.json({ error: "Unauthorized" }, { status: 401 });
393
422
  }
394
423
 
395
- // Use session.tokens.accessToken to call your backend
424
+ // Now call your backend with the access token
396
425
  const res = await fetch(`${authConfig.baseUrl}/orders`, {
397
- headers: { Authorization: `Bearer ${session.tokens!.accessToken}` },
426
+ headers: {
427
+ Authorization: `Bearer ${session.tokens!.accessToken}`,
428
+ },
398
429
  });
399
430
 
400
431
  return Response.json(await res.json());
@@ -405,9 +436,44 @@ This keeps tokens secure — they never leave the server.
405
436
 
406
437
  ---
407
438
 
408
- ### Protecting API Routes with `withAuth`
439
+ ### `getServerSession(req, config)`
440
+
441
+ Reads and validates the session in server components and API routes.
442
+
443
+ ```ts
444
+ // app/dashboard/page.tsx (server component)
445
+ import { redirect } from "next/navigation";
446
+ import { cookies } from "next/headers";
447
+ import { getServerSession } from "next-token-auth/server";
448
+ import { authConfig } from "@/lib/auth";
449
+
450
+ export default async function DashboardPage() {
451
+ const cookieStore = await cookies();
452
+ const session = await getServerSession(
453
+ { cookies: { get: (name) => cookieStore.get(name) } },
454
+ authConfig
455
+ );
456
+
457
+ if (!session.isAuthenticated) {
458
+ redirect("/login");
459
+ }
460
+
461
+ return <h1>Welcome, {session.user.name}</h1>;
462
+ }
463
+ ```
464
+
465
+ What it does:
466
+ 1. Reads the encrypted session cookie
467
+ 2. Decrypts it using your `secret`
468
+ 3. Validates token expiry
469
+ 4. Optionally fetches the user profile from your backend
470
+ 5. Returns `{ user, tokens, isAuthenticated }`
471
+
472
+ ---
473
+
474
+ ### `withAuth(config, handler, options?)`
409
475
 
410
- Wrap App Router route handlers to require authentication:
476
+ Wraps App Router route handlers to require authentication.
411
477
 
412
478
  ```ts
413
479
  // app/api/profile/route.ts
@@ -415,86 +481,109 @@ import { withAuth } from "next-token-auth/server";
415
481
  import { authConfig } from "@/lib/auth";
416
482
 
417
483
  export const GET = withAuth(authConfig, async (req, session) => {
484
+ // session.user is guaranteed to exist here
418
485
  return Response.json({ user: session.user });
419
486
  });
420
487
  ```
421
488
 
422
- Unauthenticated requests are redirected to `/login` by default. Pass `{ redirectTo: "/your-path" }` as the third argument to override.
489
+ Unauthenticated requests are redirected to `/login` by default. Override with:
490
+
491
+ ```ts
492
+ export const GET = withAuth(
493
+ authConfig,
494
+ async (req, session) => { /* ... */ },
495
+ { redirectTo: "/sign-in" }
496
+ );
497
+ ```
423
498
 
424
499
  ---
425
500
 
426
- ### Middleware (Edge Route Protection)
501
+ ### `authMiddleware(config)`
427
502
 
428
- Protect entire route groups at the edge using Next.js middleware. The middleware supports three route categories:
503
+ Creates a Next.js middleware function for edge-level route protection.
429
504
 
430
- - `public` — always accessible, no auth check
431
- - `protected` requires authentication, redirects to `loginPath` if not
432
- - `guestOnly` accessible only when NOT authenticated; authenticated users are redirected to `redirectAuthenticatedTo`
505
+ ```ts
506
+ // middleware.ts (project root, next to app/)
507
+ import { authMiddleware } from "next-token-auth/server";
508
+ import { authConfig } from "@/lib/auth";
433
509
 
434
- You can use any route naming convention you want — the library doesn't enforce `/login`, `/dashboard`, or any specific path. Everything is driven by your config.
510
+ export const middleware = authMiddleware(authConfig);
435
511
 
436
- ```ts
437
- // lib/auth.ts
438
- export const authConfig: AuthConfig = {
439
- // ...
440
- routes: {
441
- public: ["/", "/about"],
442
- guestOnly: ["/sign-in", "/sign-up"], // any names you want
443
- protected: ["/app*", "/account*"],
444
- loginPath: "/sign-in", // where unauthenticated users are sent
445
- redirectAuthenticatedTo: "/app/home", // where authenticated users are sent from guestOnly routes
446
- },
512
+ export const config = {
513
+ matcher: ["/login", "/register", "/dashboard*", "/profile*"],
447
514
  };
448
515
  ```
449
516
 
517
+ **Next.js 16+ users:** Rename the file to `proxy.ts` and change the export:
518
+
450
519
  ```ts
451
- // middleware.ts (Next.js 13–15) or proxy.ts (Next.js 16+)
452
- import { authMiddleware } from "next-token-auth/server";
453
- import { authConfig } from "@/lib/auth";
520
+ // proxy.ts
521
+ export const proxy = authMiddleware(authConfig);
522
+ ```
454
523
 
455
- // Next.js 13–15
456
- export const middleware = authMiddleware(authConfig);
524
+ ---
457
525
 
458
- // Next.js 16+
459
- export const proxy = authMiddleware(authConfig);
526
+ ## Route Protection Explained
460
527
 
461
- export const config = {
462
- matcher: ["/sign-in", "/sign-up", "/app*", "/account*"],
463
- };
528
+ The middleware supports three route categories:
529
+
530
+ ### 1. Public routes
531
+
532
+ Always accessible, no auth check. Example: homepage, about page.
533
+
534
+ ```ts
535
+ routes: {
536
+ public: ["/", "/about", "/pricing"],
537
+ }
464
538
  ```
465
539
 
466
- Some other valid setups:
540
+ ### 2. Protected routes
541
+
542
+ Require authentication. Unauthenticated users are redirected to `loginPath`.
467
543
 
468
544
  ```ts
469
- // Using /auth/* convention
470
545
  routes: {
471
- guestOnly: ["/auth/login", "/auth/register"],
472
- protected: ["/dashboard*"],
473
- loginPath: "/auth/login",
474
- redirectAuthenticatedTo: "/dashboard",
546
+ protected: ["/dashboard*", "/settings*"],
547
+ loginPath: "/login", // where to send unauthenticated users
475
548
  }
549
+ ```
550
+
551
+ Wildcard matching:
552
+ - `"/dashboard*"` matches `/dashboard`, `/dashboard/`, `/dashboard/settings`
553
+ - `"/api/admin*"` matches `/api/admin`, `/api/admin/users`
554
+
555
+ ### 3. Guest-only routes
556
+
557
+ Only accessible when NOT authenticated. Authenticated users are redirected away.
558
+
559
+ Use this for login and register pages so logged-in users can't access them.
476
560
 
477
- // Using a portal pattern
561
+ ```ts
478
562
  routes: {
479
- guestOnly: ["/portal"],
480
- protected: ["/admin*", "/workspace*"],
481
- loginPath: "/portal",
482
- redirectAuthenticatedTo: "/admin",
563
+ guestOnly: ["/login", "/register"],
564
+ redirectAuthenticatedTo: "/dashboard", // where to send authenticated users
483
565
  }
484
566
  ```
485
567
 
486
- Route resolution order inside the middleware:
568
+ ### Resolution order
569
+
570
+ When a request hits the middleware:
571
+
572
+ 1. Check if it's a `guestOnly` route → if authenticated, redirect to `redirectAuthenticatedTo`
573
+ 2. Check if it's a `public` route → always allow through
574
+ 3. Check if it's a `protected` route → if not authenticated, redirect to `loginPath`
575
+
576
+ ### Matcher vs routes config
487
577
 
488
- 1. `guestOnly` if authenticated, redirect to `redirectAuthenticatedTo`
489
- 2. `public` — always allow through
490
- 3. `protected` — require valid session, redirect to `loginPath` if missing
578
+ The `matcher` in your middleware file controls which routes Next.js runs the middleware on at all:
491
579
 
492
- Two things to keep in mind:
580
+ ```ts
581
+ export const config = {
582
+ matcher: ["/login", "/dashboard*"],
583
+ };
584
+ ```
493
585
 
494
- - Wildcard patterns use `*` at the end: `"/dashboard*"` matches `/dashboard`, `/dashboard/`, and `/dashboard/settings`
495
- - The `matcher` in `export const config` controls which routes Next.js runs the middleware on at all — make sure it covers both your protected and guest-only routes
496
- - `loginPath` defaults to `"/login"` if not set
497
- - `redirectAuthenticatedTo` defaults to `"/dashboard"` if not set
586
+ If a route isn't in the `matcher`, the middleware never runs for it — so your `routes.protected` list won't help. Make sure the `matcher` covers all routes you want to protect or mark as guest-only.
498
587
 
499
588
  ---
500
589
 
@@ -502,9 +591,9 @@ Two things to keep in mind:
502
591
 
503
592
  ### How tokens are stored
504
593
 
505
- Tokens are always stored in HttpOnly cookies (encrypted with AES-GCM). The `storage: "memory"` option is deprecated HttpOnly cookies are more secure because JavaScript in the browser cannot access them.
594
+ Tokens are stored in HttpOnly cookies, encrypted with AES-GCM. The cookie is set by the `/api/auth/login` Route Handler and read by the middleware and `getServerSession`.
506
595
 
507
- The cookie is set by the `/api/auth/login` Route Handler (created via `createAuthHandlers`) and read by the middleware and `getServerSession`.
596
+ JavaScript in the browser cannot read the cookie only the server can decrypt it.
508
597
 
509
598
  ### Session restore on page load
510
599
 
@@ -512,67 +601,31 @@ When `AuthProvider` mounts, it calls `GET /api/auth/session`, which:
512
601
 
513
602
  1. Reads the encrypted session cookie server-side
514
603
  2. Decrypts it using your `secret`
515
- 3. Checks whether the refresh token is expired
516
- 4. If a `me` endpoint is configured, fetches the user profile
604
+ 3. Checks if the refresh token is expired
605
+ 4. Fetches the user profile from your backend (if `me` endpoint is configured)
517
606
  5. Returns `{ user, isAuthenticated }` to the client
518
607
 
519
608
  The client never sees the raw tokens — only the user object and auth status.
520
609
 
521
- ### Automatic refresh
610
+ ### Automatic token refresh
522
611
 
523
- When `autoRefresh: true`, the provider calls `POST /api/auth/refresh` periodically (based on `refreshThreshold`, default 60 seconds before expiry). The Route Handler:
612
+ When `autoRefresh: true`, the provider periodically calls `POST /api/auth/refresh` (based on `refreshThreshold`, default 60 seconds before expiry).
524
613
 
614
+ The Route Handler:
525
615
  1. Reads the encrypted cookie
526
616
  2. Checks if the refresh token is still valid
527
617
  3. Calls your backend's refresh endpoint with the refresh token
528
618
  4. Encrypts the new tokens and updates the HttpOnly cookie
529
619
 
530
- ### Refresh flow
531
-
532
- ```
533
- Client detects expiry → POST /api/auth/refresh → backend refresh endpoint → new encrypted cookie set
534
- ```
535
-
536
620
  If the refresh token is expired, the session is cleared.
537
621
 
538
622
  ---
539
623
 
540
- ## Server-Side Session (`getServerSession`)
624
+ ## Backend API Requirements
541
625
 
542
- Use this in App Router server components and API routes to read the session without going through the client:
543
-
544
- ```ts
545
- // app/dashboard/page.tsx
546
- import { redirect } from "next/navigation";
547
- import { cookies } from "next/headers";
548
- import { getServerSession } from "next-token-auth/server";
549
- import { authConfig } from "@/lib/auth";
626
+ Your backend needs to implement these endpoints:
550
627
 
551
- export default async function DashboardPage() {
552
- const cookieStore = await cookies();
553
-
554
- const session = await getServerSession(
555
- { cookies: { get: (name) => cookieStore.get(name) } },
556
- authConfig
557
- );
558
-
559
- if (!session.isAuthenticated) {
560
- redirect("/login");
561
- }
562
-
563
- return <h1>Welcome, {session.user.name}</h1>;
564
- }
565
- ```
566
-
567
- `getServerSession` decrypts the session cookie, validates expiry, and attempts a server-side token refresh if the access token is near expiry but the refresh token is still valid.
568
-
569
- ---
570
-
571
- ## Backend Requirements
572
-
573
- Your API needs to implement the following contract:
574
-
575
- ### `POST /auth/login`
628
+ ### `POST /auth/login` (or whatever you set in `endpoints.login`)
576
629
 
577
630
  Request body: whatever fields you pass to `login()` (e.g. `{ email, password }`)
578
631
 
@@ -580,9 +633,13 @@ Response:
580
633
 
581
634
  ```json
582
635
  {
583
- "accessToken": "eyJ...",
584
- "refreshToken": "eyJ...",
585
- "user": { "id": "1", "email": "user@example.com", "name": "Jane" },
636
+ "accessToken": "eyJhbGc...",
637
+ "refreshToken": "eyJhbGc...",
638
+ "user": {
639
+ "id": "123",
640
+ "email": "user@example.com",
641
+ "name": "Jane Doe"
642
+ },
586
643
 
587
644
  // Optional — used by "backend" and "hybrid" expiry strategies
588
645
  "accessTokenExpiresIn": "2d",
@@ -598,39 +655,53 @@ Response:
598
655
  Request body:
599
656
 
600
657
  ```json
601
- { "refreshToken": "eyJ..." }
658
+ {
659
+ "refreshToken": "eyJhbGc..."
660
+ }
602
661
  ```
603
662
 
604
663
  Response: same shape as the login response (new `accessToken` + `refreshToken`).
605
664
 
606
- ### `GET /auth/me` _(optional)_
665
+ ### `GET /auth/me` (optional)
607
666
 
608
667
  Returns the current user object. Called after login and on session restore if the `me` endpoint is configured.
609
668
 
610
- ### `POST /auth/logout` _(optional)_
669
+ Response:
670
+
671
+ ```json
672
+ {
673
+ "id": "123",
674
+ "email": "user@example.com",
675
+ "name": "Jane Doe"
676
+ }
677
+ ```
678
+
679
+ ### `POST /auth/logout` (optional)
611
680
 
612
- Called on logout. Failure is silently ignored — tokens are always cleared locally regardless.
681
+ Called on logout. Failure is silently ignored — the session cookie is always cleared locally regardless.
613
682
 
614
683
  ---
615
684
 
616
685
  ## Expiry Formats
617
686
 
618
- The `parseExpiry` utility accepts:
687
+ The library accepts expiry values in multiple formats:
688
+
689
+ | Input | Seconds | Human-readable |
690
+ |---------|-----------|----------------|
691
+ | `900` | 900 | 15 minutes |
692
+ | `"15m"` | 900 | 15 minutes |
693
+ | `"2h"` | 7,200 | 2 hours |
694
+ | `"2d"` | 172,800 | 2 days |
695
+ | `"7d"` | 604,800 | 7 days |
696
+ | `"1w"` | 604,800 | 1 week |
619
697
 
620
- | Input | Seconds |
621
- |---------|-----------|
622
- | `900` | 900 |
623
- | `"15m"` | 900 |
624
- | `"2h"` | 7 200 |
625
- | `"2d"` | 172 800 |
626
- | `"7d"` | 604 800 |
627
- | `"1w"` | 604 800 |
698
+ Supported units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days), `w` (weeks)
628
699
 
629
700
  ### Expiry strategies
630
701
 
631
- | Strategy | Behaviour |
702
+ | Strategy | Behavior |
632
703
  |-----------|------------------------------------------------------------------|
633
- | `backend` | Use only the expiry values returned by the API |
704
+ | `backend` | Use only the expiry values returned by your API |
634
705
  | `config` | Use only the values set in `expiry` config |
635
706
  | `hybrid` | API response first; fall back to config if not present (default) |
636
707
 
@@ -638,6 +709,79 @@ The `parseExpiry` utility accepts:
638
709
 
639
710
  ---
640
711
 
712
+ ## Configuration Reference
713
+
714
+ ### `ClientAuthConfig` (for `AuthProvider`)
715
+
716
+ ```ts
717
+ interface ClientAuthConfig {
718
+ token?: {
719
+ cookieName?: string; // default: "next-token-auth.session"
720
+ };
721
+
722
+ routes?: {
723
+ loginPath?: string; // default: "/login"
724
+ redirectAuthenticatedTo?: string; // default: "/dashboard"
725
+ };
726
+
727
+ autoRefresh?: boolean; // default: false
728
+ refreshThreshold?: number; // seconds before expiry to refresh (default: 60)
729
+
730
+ onLogin?: (session: AuthSession) => void;
731
+ onLogout?: () => void;
732
+ }
733
+ ```
734
+
735
+ ### `AuthConfig` (for server-side functions)
736
+
737
+ ```ts
738
+ interface AuthConfig<User = unknown> {
739
+ baseUrl: string; // Your backend API base URL
740
+
741
+ endpoints: {
742
+ login: string; // required
743
+ refresh: string; // required
744
+ register?: string;
745
+ logout?: string;
746
+ me?: string;
747
+ };
748
+
749
+ routes?: {
750
+ public: string[]; // always accessible
751
+ protected: string[]; // require auth
752
+ guestOnly?: string[]; // only when NOT authenticated
753
+ loginPath?: string; // default: "/login"
754
+ redirectAuthenticatedTo?: string; // default: "/dashboard"
755
+ };
756
+
757
+ token: {
758
+ storage: "cookie" | "memory";
759
+ cookieName?: string;
760
+ secure?: boolean; // default: true
761
+ sameSite?: "strict" | "lax" | "none"; // default: "lax"
762
+ };
763
+
764
+ secret: string; // AES-GCM encryption key
765
+
766
+ autoRefresh?: boolean;
767
+ refreshThreshold?: number;
768
+
769
+ expiry?: {
770
+ accessTokenExpiresIn?: number | string;
771
+ refreshTokenExpiresIn?: number | string;
772
+ strategy?: "backend" | "config" | "hybrid";
773
+ };
774
+
775
+ fetchFn?: typeof fetch; // custom fetch for testing
776
+
777
+ onLogin?: (session: AuthSession<User>) => void;
778
+ onLogout?: () => void;
779
+ onRefreshError?: (error: unknown) => void;
780
+ }
781
+ ```
782
+
783
+ ---
784
+
641
785
  ## TypeScript Types
642
786
 
643
787
  ```ts
@@ -651,7 +795,7 @@ interface AuthTokens {
651
795
  accessToken: string;
652
796
  refreshToken: string;
653
797
  accessTokenExpiresAt: number; // Unix timestamp in ms
654
- refreshTokenExpiresAt?: number; // Unix timestamp in ms
798
+ refreshTokenExpiresAt?: number;
655
799
  }
656
800
 
657
801
  interface LoginResponse<User = unknown> {
@@ -662,34 +806,102 @@ interface LoginResponse<User = unknown> {
662
806
  accessTokenExpiresIn?: number | string;
663
807
  refreshTokenExpiresIn?: number | string;
664
808
  }
809
+ ```
665
810
 
666
- // Server-side config (used in createAuthHandlers, middleware, getServerSession)
667
- interface AuthConfig<User = unknown> {
668
- baseUrl: string;
669
- secret: string; // SERVER-SIDE ONLY
670
- endpoints: { login: string; refresh: string; logout?: string; me?: string };
671
- token: { storage: "cookie" | "memory"; cookieName?: string; secure?: boolean; sameSite?: string };
672
- routes?: { public: string[]; protected: string[]; guestOnly?: string[]; loginPath?: string; redirectAuthenticatedTo?: string };
673
- expiry?: { accessTokenExpiresIn?: number | string; refreshTokenExpiresIn?: number | string; strategy?: "backend" | "config" | "hybrid" };
674
- autoRefresh?: boolean;
675
- refreshThreshold?: number;
811
+ All types are exported from `next-token-auth`.
812
+
813
+ ---
814
+
815
+ ## Common Patterns
816
+
817
+ ### Custom user type
818
+
819
+ ```ts
820
+ interface MyUser {
821
+ id: string;
822
+ email: string;
823
+ role: "admin" | "user";
676
824
  }
677
825
 
678
- // Client-side config (used in AuthProvider)
679
- interface ClientAuthConfig {
680
- token?: { cookieName?: string };
681
- routes?: { loginPath?: string; redirectAuthenticatedTo?: string };
682
- autoRefresh?: boolean;
683
- refreshThreshold?: number;
684
- onLogin?: (session: AuthSession) => void;
685
- onLogout?: () => void;
826
+ const { session } = useAuth<MyUser>();
827
+ console.log(session.user?.role);
828
+ ```
829
+
830
+ ### Logout with redirect
831
+
832
+ ```ts
833
+ const { logout } = useAuth();
834
+
835
+ async function handleLogout() {
836
+ await logout();
837
+ window.location.href = "/";
686
838
  }
839
+ ```
840
+
841
+ ### Conditional rendering based on auth
842
+
843
+ ```ts
844
+ const { isAuthenticated } = useSession();
845
+
846
+ return (
847
+ <nav>
848
+ {isAuthenticated ? (
849
+ <a href="/dashboard">Dashboard</a>
850
+ ) : (
851
+ <a href="/login">Sign in</a>
852
+ )}
853
+ </nav>
854
+ );
855
+ ```
856
+
857
+ ### Server-side redirect in a server component
858
+
859
+ ```ts
860
+ import { redirect } from "next/navigation";
861
+ import { getServerSession } from "next-token-auth/server";
862
+
863
+ export default async function AdminPage() {
864
+ const session = await getServerSession(req, authConfig);
865
+
866
+ if (!session.isAuthenticated) {
867
+ redirect("/login");
868
+ }
687
869
 
688
- type ExpiryInput = number | string;
689
- type ExpiryStrategy = "backend" | "config" | "hybrid";
870
+ if (session.user.role !== "admin") {
871
+ redirect("/dashboard");
872
+ }
873
+
874
+ return <h1>Admin Panel</h1>;
875
+ }
690
876
  ```
691
877
 
692
- All types are exported from the root `next-token-auth` import.
878
+ ---
879
+
880
+ ## Troubleshooting
881
+
882
+ ### "Cannot find module 'next-token-auth/server'"
883
+
884
+ Run `npm run build` (or `pnpm build`) to generate the `dist/` folder. The package uses subpath exports (`/server`, `/react`) which require a build step.
885
+
886
+ ### Middleware always redirects to login
887
+
888
+ Check three things:
889
+
890
+ 1. The `matcher` in your `middleware.ts` includes the route you're testing
891
+ 2. The route is listed in `routes.protected` or not listed in `routes.public`
892
+ 3. You're actually logged in — check the Application tab in DevTools for the session cookie
893
+
894
+ ### "secret is undefined"
895
+
896
+ Make sure `AUTH_SECRET` is set in your `.env.local` file and you're importing `authConfig` (not `clientAuthConfig`) in your Route Handler and middleware.
897
+
898
+ ### Session is lost on page reload
899
+
900
+ The session should persist via the HttpOnly cookie. If it's not:
901
+
902
+ 1. Check that `cookieName` matches in both `authConfig` and `clientAuthConfig`
903
+ 2. Verify the cookie exists in DevTools → Application → Cookies
904
+ 3. Make sure `app/api/auth/[action]/route.ts` exists and exports `createAuthHandlers(authConfig)`
693
905
 
694
906
  ---
695
907
 
@@ -702,15 +914,14 @@ All types are exported from the root `next-token-auth` import.
702
914
 
703
915
  ---
704
916
 
705
- ## Security Notes
917
+ ## Security
706
918
 
707
- - All tokens are stored in HttpOnly cookies — JavaScript in the browser cannot read them
708
- - Session cookies are AES-GCM encrypted server-side using your `secret`
919
+ - Tokens are stored in HttpOnly cookies — JavaScript cannot read them
920
+ - Cookies are AES-GCM encrypted server-side using your `secret`
709
921
  - The `secret` never leaves the server — it's only used in Route Handlers, middleware, and `getServerSession`
710
922
  - `AuthProvider` receives `ClientAuthConfig` which does not contain `secret` or `baseUrl`
711
- - Use a random 32-character string for `secret` in production — never commit it
712
923
  - Cookies use `Secure` and `SameSite` flags by default for CSRF protection
713
- - The `"memory"` storage mode is no longer recommendedHttpOnly cookies are more secure
924
+ - Use a random 32-character string for `secret` in production never commit it
714
925
 
715
926
  ---
716
927
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-token-auth",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Production-grade authentication library for Next.js (App Router & Pages Router)",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",