@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.
- package/README.md +93 -70
- 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
|
-
>
|
|
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 {
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
93
|
+
- **NEVER include just `/` in publicPaths** - it would match ALL paths!
|
|
86
94
|
|
|
87
|
-
|
|
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
|
-
|
|
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)
|
|
289
|
-
| `me()` | Get user from API (full data)
|
|
290
|
-
| `login(email, password)` | Login, sets cookies
|
|
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,
|
|
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
|
-
|
|
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.
|
|
331
|
-
4.
|
|
332
|
-
5.
|
|
333
|
-
6.
|
|
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:**
|
|
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.
|
|
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",
|