next-token-auth 1.0.15 → 1.0.17

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