@artatol-acp/auth-nextjs 0.5.18 → 0.5.19

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 +93 -70
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -32,7 +32,7 @@ The SDK uses **httpOnly cookies** for secure token storage:
32
32
 
33
33
  ### 1. Set Up Proxy/Middleware (REQUIRED)
34
34
 
35
- > **⚠️ CRITICAL:** The proxy is **required** for automatic token refresh. Without it, users will be logged out when their access token expires (every 5 minutes).
35
+ > **CRITICAL:** The proxy is **required** for automatic token refresh. Without it, users will be logged out when their access token expires (every 5 minutes).
36
36
 
37
37
  **Why is proxy required?**
38
38
 
@@ -56,8 +56,9 @@ Server Components **cannot** set cookies. When a user visits a protected page wi
56
56
  // src/proxy.ts (Next.js 16+)
57
57
  // or src/middleware.ts (Next.js 14-15)
58
58
 
59
- import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/proxy';
60
- // For Next.js 14-15: import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware';
59
+ import { NextRequest, NextResponse } from 'next/server'
60
+ import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/proxy'
61
+ // For Next.js 14-15: import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware'
61
62
 
62
63
  const acpMiddleware = createACPAuthMiddleware({
63
64
  baseUrl: process.env.ACP_AUTH_URL!,
@@ -68,40 +69,37 @@ const acpMiddleware = createACPAuthMiddleware({
68
69
  cookies: {
69
70
  domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
70
71
  },
71
- });
72
+ })
72
73
 
73
- export default acpMiddleware;
74
+ export default async function middleware(request: NextRequest) {
75
+ // Homepage is public - handle with exact match
76
+ if (request.nextUrl.pathname === '/') {
77
+ return NextResponse.next()
78
+ }
79
+
80
+ return acpMiddleware(request)
81
+ }
74
82
 
75
83
  export const config = {
76
84
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|icon).*)'],
77
- };
85
+ }
78
86
  ```
79
87
 
80
- **⚠️ WARNING about `publicPaths`:**
88
+ **WARNING about `publicPaths`:**
81
89
 
82
90
  The `publicPaths` option uses **prefix matching** (`pathname.startsWith(path)`). This means:
83
91
 
84
92
  - `/login` matches `/login`, `/login/`, `/login/callback`, etc.
85
- - `/` would match **ALL paths** - never include just `/` in publicPaths!
93
+ - **NEVER include just `/` in publicPaths** - it would match ALL paths!
86
94
 
87
- If you need the homepage (`/`) to be public, handle it separately:
88
-
89
- ```typescript
90
- export default async function middleware(request: NextRequest) {
91
- // Homepage is public - skip auth
92
- if (request.nextUrl.pathname === '/') {
93
- return NextResponse.next();
94
- }
95
- return acpMiddleware(request);
96
- }
97
- ```
95
+ Always handle the homepage (`/`) with an exact match check as shown above.
98
96
 
99
97
  ### 2. Create API Route Handlers
100
98
 
101
99
  Create `app/api/auth/[action]/route.ts`:
102
100
 
103
101
  ```typescript
104
- import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers';
102
+ import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers'
105
103
 
106
104
  const { authHandler } = createAuthHandlers({
107
105
  baseUrl: process.env.ACP_AUTH_URL!,
@@ -109,15 +107,15 @@ const { authHandler } = createAuthHandlers({
109
107
  cookies: {
110
108
  domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
111
109
  },
112
- });
110
+ })
113
111
 
114
- export const POST = authHandler;
112
+ export const POST = authHandler
115
113
  ```
116
114
 
117
115
  This creates these endpoints:
118
116
  - `POST /api/auth/login` - Login
119
117
  - `POST /api/auth/logout` - Logout
120
- - `POST /api/auth/session` - Refresh session & get user
118
+ - `POST /api/auth/session` - Refresh session & get user (used by client-side refresh)
121
119
  - `POST /api/auth/register` - Register
122
120
  - `POST /api/auth/verify-2fa` - Complete 2FA login
123
121
  - `POST /api/auth/resend-verification` - Resend verification email
@@ -129,7 +127,7 @@ This creates these endpoints:
129
127
  Create `lib/auth.ts`:
130
128
 
131
129
  ```typescript
132
- import { initACPAuth } from '@artatol-acp/auth-nextjs/server';
130
+ import { initACPAuth } from '@artatol-acp/auth-nextjs/server'
133
131
 
134
132
  // Initialize once at module load
135
133
  initACPAuth({
@@ -139,7 +137,7 @@ initACPAuth({
139
137
  cookies: {
140
138
  domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
141
139
  },
142
- });
140
+ })
143
141
 
144
142
  // Re-export server functions
145
143
  export {
@@ -155,7 +153,7 @@ export {
155
153
  resetPassword,
156
154
  deleteAccount,
157
155
  refreshAccessToken,
158
- } from '@artatol-acp/auth-nextjs/server';
156
+ } from '@artatol-acp/auth-nextjs/server'
159
157
  ```
160
158
 
161
159
  ### 4. Add Client Provider
@@ -163,11 +161,13 @@ export {
163
161
  In your root layout (`app/layout.tsx`):
164
162
 
165
163
  ```typescript
166
- import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client';
167
- import { getUser } from '@/lib/auth';
164
+ import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client'
165
+ import { getUser } from '@/lib/auth'
168
166
 
169
167
  export default async function RootLayout({ children }) {
170
- const user = await getUser();
168
+ // getUser() is fast - just verifies JWT locally, no API call
169
+ // Returns { id, email } or null
170
+ const user = await getUser()
171
171
 
172
172
  return (
173
173
  <html>
@@ -177,78 +177,89 @@ export default async function RootLayout({ children }) {
177
177
  </ACPAuthProvider>
178
178
  </body>
179
179
  </html>
180
- );
180
+ )
181
181
  }
182
182
  ```
183
183
 
184
+ **How client-side refresh works:**
185
+
186
+ When `initialUser` is `null` (no access_token or expired), `ACPAuthProvider` automatically calls `/api/auth/session` to attempt a refresh using the refresh_token. If successful, the user state updates and new cookies are set. This enables seamless client-side navigation even when access_token has expired.
187
+
184
188
  ## Usage
185
189
 
186
190
  ### Server Components
187
191
 
188
192
  ```typescript
189
- import { getUser, me } from '@/lib/auth';
193
+ import { getUser, me } from '@/lib/auth'
190
194
 
191
195
  export default async function ProfilePage() {
192
196
  // Fast local JWT verification (returns { id, email } or null)
193
- const user = await getUser();
197
+ const user = await getUser()
194
198
 
195
199
  // Or fetch full user from API (returns { id, email, twoFactorEnabled } or null)
196
- const fullUser = await me();
200
+ const fullUser = await me()
197
201
 
198
202
  if (!user) {
199
- return <div>Not logged in</div>;
203
+ return <div>Not logged in</div>
200
204
  }
201
205
 
202
- return <div>Welcome {user.email}</div>;
206
+ return <div>Welcome {user.email}</div>
203
207
  }
204
208
  ```
205
209
 
210
+ **When to use `getUser()` vs `me()`:**
211
+
212
+ | Function | Speed | Returns | Use when |
213
+ |----------|-------|---------|----------|
214
+ | `getUser()` | Fast (local JWT) | `{ id, email }` | You only need basic user info |
215
+ | `me()` | Slower (API call) | `{ id, email, twoFactorEnabled }` | You need `twoFactorEnabled` status |
216
+
206
217
  ### Server Actions
207
218
 
208
219
  ```typescript
209
- 'use server';
220
+ 'use server'
210
221
 
211
- import { login, logout, verify2FALogin } from '@/lib/auth';
212
- import { redirect } from 'next/navigation';
222
+ import { login, logout, verify2FALogin } from '@/lib/auth'
223
+ import { redirect } from 'next/navigation'
213
224
 
214
225
  export async function loginAction(formData: FormData) {
215
- const email = formData.get('email') as string;
216
- const password = formData.get('password') as string;
226
+ const email = formData.get('email') as string
227
+ const password = formData.get('password') as string
217
228
 
218
- const result = await login(email, password);
229
+ const result = await login(email, password)
219
230
 
220
231
  if ('requiresTwoFactor' in result) {
221
- return { requires2FA: true, tempToken: result.tempToken };
232
+ return { requires2FA: true, tempToken: result.tempToken }
222
233
  }
223
234
 
224
- redirect('/dashboard');
235
+ redirect('/dashboard')
225
236
  }
226
237
 
227
238
  export async function logoutAction() {
228
- await logout();
229
- redirect('/login');
239
+ await logout()
240
+ redirect('/login')
230
241
  }
231
242
  ```
232
243
 
233
244
  ### Client Components
234
245
 
235
246
  ```typescript
236
- 'use client';
247
+ 'use client'
237
248
 
238
- import { useAuth } from '@artatol-acp/auth-nextjs/client';
249
+ import { useAuth } from '@artatol-acp/auth-nextjs/client'
239
250
 
240
251
  export function UserMenu() {
241
- const { user, logout, isLoading } = useAuth();
252
+ const { user, logout, isLoading } = useAuth()
242
253
 
243
- if (isLoading) return <div>Loading...</div>;
244
- if (!user) return <a href="/login">Sign In</a>;
254
+ if (isLoading) return <div>Loading...</div>
255
+ if (!user) return <a href="/login">Sign In</a>
245
256
 
246
257
  return (
247
258
  <div>
248
259
  <span>{user.email}</span>
249
260
  <button onClick={logout}>Logout</button>
250
261
  </div>
251
- );
262
+ )
252
263
  }
253
264
  ```
254
265
 
@@ -277,7 +288,7 @@ createACPAuthMiddleware({
277
288
  secure?: boolean, // HTTPS only (default: NODE_ENV === 'production')
278
289
  sameSite?: 'strict' | 'lax' | 'none', // (default: 'lax')
279
290
  },
280
- });
291
+ })
281
292
  ```
282
293
 
283
294
  ### Server Functions
@@ -285,9 +296,9 @@ createACPAuthMiddleware({
285
296
  | Function | Description |
286
297
  |----------|-------------|
287
298
  | `initACPAuth(options)` | Initialize auth configuration (call once) |
288
- | `getUser()` | Get user from JWT (local, fast) `{ id, email }` or `null` |
289
- | `me()` | Get user from API (full data) `{ id, email, twoFactorEnabled }` or `null` |
290
- | `login(email, password)` | Login, sets cookies `{ accessToken, user }` or `{ requiresTwoFactor, tempToken }` |
299
+ | `getUser()` | Get user from JWT (local, fast) -> `{ id, email }` or `null` |
300
+ | `me()` | Get user from API (full data) -> `{ id, email, twoFactorEnabled }` or `null` |
301
+ | `login(email, password)` | Login, sets cookies -> `{ accessToken, user }` or `{ requiresTwoFactor, tempToken }` |
291
302
  | `verify2FALogin(tempToken, code)` | Complete 2FA login |
292
303
  | `logout()` | Logout, clears cookies |
293
304
  | `register(email, password)` | Register new user |
@@ -303,13 +314,13 @@ createACPAuthMiddleware({
303
314
  ```typescript
304
315
  const {
305
316
  user, // Current user or null
306
- isLoading, // True during initial load
317
+ isLoading, // True during initial load or refresh
307
318
  login, // (email, password) => Promise<{ requiresTwoFactor?, tempToken? }>
308
319
  verify2FA, // (tempToken, code) => Promise<void>
309
320
  logout, // () => Promise<void>
310
- refresh, // () => Promise<boolean>
321
+ refresh, // () => Promise<boolean> - manually trigger session refresh
311
322
  resendVerification, // (email) => Promise<void>
312
- } = useAuth();
323
+ } = useAuth()
313
324
  ```
314
325
 
315
326
  ### ACPAuthProvider Props
@@ -317,7 +328,7 @@ const {
317
328
  ```typescript
318
329
  <ACPAuthProvider
319
330
  apiBasePath="/api/auth" // Optional, default: "/api/auth"
320
- initialUser={user} // Optional, SSR user data
331
+ initialUser={user} // Optional, from getUser() - { id, email } or null
321
332
  >
322
333
  {children}
323
334
  </ACPAuthProvider>
@@ -325,27 +336,39 @@ const {
325
336
 
326
337
  ## Token Refresh Flow
327
338
 
328
- 1. **User visits protected page** with expired `access_token`
339
+ ### Server-side (hard refresh, direct URL access)
340
+
341
+ 1. User visits protected page with expired `access_token`
329
342
  2. **Proxy intercepts** the request before page renders
330
- 3. **Proxy calls** auth server `/refresh` endpoint with `refresh_token`
331
- 4. **Auth server** validates refresh token, rotates it, returns new tokens
332
- 5. **Proxy sets** new `access_token` and `refresh_token` cookies
333
- 6. **Page renders** with valid tokens
343
+ 3. Proxy calls auth server `/refresh` with `refresh_token`
344
+ 4. Auth server validates, **rotates** refresh token, returns new tokens
345
+ 5. Proxy sets new `access_token` and `refresh_token` cookies
346
+ 6. Page renders with valid tokens
347
+
348
+ ### Client-side (navigation within app)
349
+
350
+ 1. User clicks link to protected page (client-side navigation)
351
+ 2. Proxy does NOT run (client navigation doesn't hit server)
352
+ 3. `ACPAuthProvider` detects `initialUser` is `null`
353
+ 4. Calls `/api/auth/session` which refreshes tokens and sets cookies
354
+ 5. User state updates, navigation proceeds
355
+
356
+ **If API handlers are missing:** Client-side refresh won't work, user gets logged out on navigation when access_token expires.
334
357
 
335
- **If proxy is missing:** Step 2-5 would happen during page render, but cookies can't be set from Server Components, so the new tokens are lost and the old refresh token is now invalid → user gets logged out.
358
+ **If proxy is missing:** Server-side refresh won't work, user gets logged out on hard refresh when access_token expires.
336
359
 
337
360
  ## Error Handling
338
361
 
339
362
  ```typescript
340
- import { ACPAuthError } from '@artatol-acp/auth-nextjs/server';
363
+ import { ACPAuthError } from '@artatol-acp/auth-nextjs/server'
341
364
 
342
365
  try {
343
- await login(email, password);
366
+ await login(email, password)
344
367
  } catch (error) {
345
368
  if (error instanceof ACPAuthError) {
346
- console.error('Status:', error.statusCode);
347
- console.error('Message:', error.message);
348
- console.error('Code:', error.code);
369
+ console.error('Status:', error.statusCode)
370
+ console.error('Message:', error.message)
371
+ console.error('Code:', error.code)
349
372
  }
350
373
  }
351
374
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artatol-acp/auth-nextjs",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "Next.js SDK for Artatol Cloud Platform Authentication with support for App Router, Server Actions, and Middleware",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",