@artatol-acp/auth-nextjs 0.3.8 → 0.4.0

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 CHANGED
@@ -2,11 +2,64 @@
2
2
 
3
3
  Next.js SDK for Artatol Cloud Platform Authentication with support for App Router, Server Actions, Middleware, and automatic token refresh.
4
4
 
5
+ ## Changelog
6
+
7
+ ### v0.4.0
8
+
9
+ **Breaking Changes:**
10
+ - `ACPAuthProvider` no longer requires `baseUrl` prop - it now uses local API routes
11
+ - `initACPAuth()` no longer returns an `ACPAuthClient` instance
12
+
13
+ **New Features:**
14
+ - **New `handlers` export**: Pre-built API route handlers for auth operations
15
+ ```typescript
16
+ import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers';
17
+ ```
18
+ - **SSR-first architecture**: `ACPAuthProvider` now accepts `initialUser` prop for SSR hydration
19
+ - **New `verify2FALogin()` server function**: Complete 2FA login flow on the server
20
+ - **New `verify2FA()` client method**: Complete 2FA login from client components
21
+
22
+ **Improvements:**
23
+ - **httpOnly cookies**: All tokens are now stored in httpOnly cookies (XSS protection)
24
+ - **Proper cookie handling**: `login()` now automatically sets both `access_token` and `refresh_token` cookies
25
+ - **Server-side refresh**: `refreshAccessToken()` now properly forwards cookies to auth server
26
+ - **Better error messages**: Clear error when `baseUrl` is missing
27
+ - **Configurable cookies**: Cookie options (path, secure, sameSite) are now configurable
28
+
29
+ **Migration Guide:**
30
+
31
+ 1. Create API route handlers:
32
+ ```typescript
33
+ // app/api/auth/[action]/route.ts
34
+ import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers';
35
+
36
+ const { authHandler } = createAuthHandlers({
37
+ baseUrl: process.env.ACP_AUTH_URL!,
38
+ });
39
+
40
+ export const POST = authHandler;
41
+ ```
42
+
43
+ 2. Update `ACPAuthProvider`:
44
+ ```typescript
45
+ // Before
46
+ <ACPAuthProvider baseUrl={process.env.NEXT_PUBLIC_ACP_AUTH_URL}>
47
+
48
+ // After
49
+ <ACPAuthProvider initialUser={user}>
50
+ ```
51
+
52
+ 3. Handle 2FA in login:
53
+ ```typescript
54
+ const result = await login(email, password);
55
+ if (result.requiresTwoFactor) {
56
+ // Show 2FA form, then call verify2FA(tempToken, code)
57
+ }
58
+ ```
59
+
5
60
  ## Installation
6
61
 
7
62
  ```bash
8
- npm install @artatol-acp/auth-nextjs
9
- # or
10
63
  pnpm add @artatol-acp/auth-nextjs
11
64
  ```
12
65
 
@@ -14,67 +67,94 @@ pnpm add @artatol-acp/auth-nextjs
14
67
 
15
68
  Before using this SDK, you need to obtain from the ACP AUTH service:
16
69
 
17
- 1. **API Key** (required) - Contact your system administrator
18
- 2. **Base URL** (required) - The auth service URL (e.g., `https://sso.artatol.net`)
19
- 3. **JWT Public Key** (optional) - Only needed for local JWT verification with `getUser()`. Download it from:
70
+ 1. **Base URL** (required) - The auth service URL (e.g., `https://sso.artatol.net`)
71
+ 2. **API Key** (optional) - Contact your system administrator if required
72
+ 3. **JWT Public Key** (required for `getUser()`) - For local JWT verification. Download it from:
20
73
  ```bash
21
74
  curl https://sso.artatol.net/public-key > public.pem
22
75
  ```
23
76
 
24
- **Note:** Without the public key, you can still use all auth operations (login, register, logout, 2FA, password reset, etc.), but you must use the `me()` function instead of `getUser()` for user verification, which makes an API call to the auth service.
77
+ ## Quick Start
25
78
 
26
- ## Setup
79
+ The SDK uses **httpOnly cookies** for secure token storage. This means:
80
+ - Tokens are NOT accessible from JavaScript (XSS protection)
81
+ - All auth operations must go through your Next.js server
82
+ - The SDK provides API route handlers to proxy requests to the auth server
27
83
 
28
- ### 1. Initialize Server-side
84
+ ### 1. Create API Route Handlers
29
85
 
30
- Create `lib/auth.ts`:
86
+ Create `app/api/auth/[action]/route.ts`:
31
87
 
32
88
  ```typescript
33
- import { initACPAuth } from '@artatol-acp/auth-nextjs/server';
34
- import { readFileSync } from 'fs';
89
+ import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers';
35
90
 
36
- const publicKey = readFileSync('./keys/public.pem', 'utf-8');
37
-
38
- export const auth = initACPAuth({
39
- baseUrl: process.env.ACP_AUTH_URL || 'https://sso.artatol.com',
40
- apiKey: process.env.ACP_AUTH_API_KEY!,
41
- jwtPublicKey: publicKey,
91
+ const { authHandler } = createAuthHandlers({
92
+ baseUrl: process.env.ACP_AUTH_URL!,
93
+ apiKey: process.env.ACP_AUTH_API_KEY,
42
94
  });
95
+
96
+ export const POST = authHandler;
43
97
  ```
44
98
 
45
- ### 2. Add Middleware (Optional)
99
+ This creates the following endpoints:
100
+ - `POST /api/auth/login` - Login
101
+ - `POST /api/auth/logout` - Logout
102
+ - `POST /api/auth/session` - Refresh session & get user
103
+ - `POST /api/auth/register` - Register
104
+ - `POST /api/auth/verify-2fa` - Complete 2FA login
105
+ - `POST /api/auth/resend-verification` - Resend verification email
106
+ - `POST /api/auth/forgot-password` - Request password reset
107
+ - `POST /api/auth/reset-password` - Reset password
46
108
 
47
- Create `middleware.ts`:
109
+ ### 2. Initialize Server-side Auth
110
+
111
+ Create `lib/auth.ts`:
48
112
 
49
113
  ```typescript
50
- import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware';
114
+ import { initACPAuth } from '@artatol-acp/auth-nextjs/server';
51
115
  import { readFileSync } from 'fs';
52
116
 
53
117
  const publicKey = readFileSync('./keys/public.pem', 'utf-8');
54
118
 
55
- export const middleware = createACPAuthMiddleware({
119
+ initACPAuth({
120
+ baseUrl: process.env.ACP_AUTH_URL!,
121
+ apiKey: process.env.ACP_AUTH_API_KEY,
56
122
  jwtPublicKey: publicKey,
57
- publicPaths: ['/login', '/register', '/forgot-password'],
58
- loginPath: '/login',
59
123
  });
60
124
 
61
- export const config = {
62
- matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
63
- };
125
+ // Re-export server functions
126
+ export {
127
+ getUser,
128
+ me,
129
+ login,
130
+ logout,
131
+ register,
132
+ verify2FALogin,
133
+ verifyEmail,
134
+ resendVerificationEmail,
135
+ forgotPassword,
136
+ resetPassword,
137
+ deleteAccount,
138
+ refreshAccessToken,
139
+ } from '@artatol-acp/auth-nextjs/server';
64
140
  ```
65
141
 
66
- ### 3. Add Client Provider (Optional)
142
+ ### 3. Add Client Provider
67
143
 
68
144
  In your root layout (`app/layout.tsx`):
69
145
 
70
146
  ```typescript
71
147
  import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client';
148
+ import { getUser } from '@/lib/auth';
149
+
150
+ export default async function RootLayout({ children }) {
151
+ // SSR: Get initial user on server
152
+ const user = await getUser();
72
153
 
73
- export default function RootLayout({ children }) {
74
154
  return (
75
155
  <html>
76
156
  <body>
77
- <ACPAuthProvider baseUrl={process.env.NEXT_PUBLIC_ACP_AUTH_URL}>
157
+ <ACPAuthProvider initialUser={user}>
78
158
  {children}
79
159
  </ACPAuthProvider>
80
160
  </body>
@@ -85,14 +165,18 @@ export default function RootLayout({ children }) {
85
165
 
86
166
  ## Usage
87
167
 
88
- ### Server Components
168
+ ### Server Components (SSR)
89
169
 
90
170
  ```typescript
91
- import { getUser } from '@artatol-acp/auth-nextjs/server';
171
+ import { getUser, me } from '@/lib/auth';
92
172
 
93
173
  export default async function ProfilePage() {
174
+ // Fast local JWT verification (returns { id, email })
94
175
  const user = await getUser();
95
176
 
177
+ // Or fetch full user from API (returns { id, email, twoFactorEnabled })
178
+ const fullUser = await me();
179
+
96
180
  if (!user) {
97
181
  return <div>Not logged in</div>;
98
182
  }
@@ -110,7 +194,7 @@ export default async function ProfilePage() {
110
194
  ```typescript
111
195
  'use server';
112
196
 
113
- import { login, register, logout } from '@artatol-acp/auth-nextjs/server';
197
+ import { login, register, logout, verify2FALogin } from '@/lib/auth';
114
198
  import { redirect } from 'next/navigation';
115
199
 
116
200
  export async function loginAction(formData: FormData) {
@@ -120,19 +204,22 @@ export async function loginAction(formData: FormData) {
120
204
  const result = await login(email, password);
121
205
 
122
206
  if ('requiresTwoFactor' in result) {
123
- // Handle 2FA
124
207
  return { requires2FA: true, tempToken: result.tempToken };
125
208
  }
126
209
 
127
210
  redirect('/dashboard');
128
211
  }
129
212
 
213
+ export async function verify2FAAction(tempToken: string, code: string) {
214
+ await verify2FALogin(tempToken, code);
215
+ redirect('/dashboard');
216
+ }
217
+
130
218
  export async function registerAction(formData: FormData) {
131
219
  const email = formData.get('email') as string;
132
220
  const password = formData.get('password') as string;
133
221
 
134
222
  await register(email, password);
135
- // User will receive verification email
136
223
  redirect('/check-email');
137
224
  }
138
225
 
@@ -148,29 +235,50 @@ export async function logoutAction() {
148
235
  'use client';
149
236
 
150
237
  import { useAuth } from '@artatol-acp/auth-nextjs/client';
238
+ import { useState } from 'react';
151
239
 
152
240
  export function LoginForm() {
153
- const { login, user, isLoading } = useAuth();
241
+ const { login, verify2FA, user, isLoading } = useAuth();
242
+ const [tempToken, setTempToken] = useState<string | null>(null);
154
243
 
155
- const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
244
+ const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
156
245
  e.preventDefault();
157
246
  const formData = new FormData(e.currentTarget);
158
247
 
159
248
  try {
160
- await login(
249
+ const result = await login(
161
250
  formData.get('email') as string,
162
251
  formData.get('password') as string
163
252
  );
253
+
254
+ if (result.requiresTwoFactor) {
255
+ setTempToken(result.tempToken!);
256
+ }
164
257
  } catch (error) {
165
258
  console.error('Login failed:', error);
166
259
  }
167
260
  };
168
261
 
262
+ const handle2FA = async (e: React.FormEvent<HTMLFormElement>) => {
263
+ e.preventDefault();
264
+ const code = new FormData(e.currentTarget).get('code') as string;
265
+ await verify2FA(tempToken!, code);
266
+ };
267
+
169
268
  if (isLoading) return <div>Loading...</div>;
170
269
  if (user) return <div>Welcome {user.email}</div>;
171
270
 
271
+ if (tempToken) {
272
+ return (
273
+ <form onSubmit={handle2FA}>
274
+ <input name="code" placeholder="2FA Code" required />
275
+ <button type="submit">Verify</button>
276
+ </form>
277
+ );
278
+ }
279
+
172
280
  return (
173
- <form onSubmit={handleSubmit}>
281
+ <form onSubmit={handleLogin}>
174
282
  <input name="email" type="email" required />
175
283
  <input name="password" type="password" required />
176
284
  <button type="submit">Login</button>
@@ -179,505 +287,177 @@ export function LoginForm() {
179
287
  }
180
288
  ```
181
289
 
182
- ### API Routes
290
+ ## Middleware (Optional)
291
+
292
+ Create `middleware.ts` for route protection:
183
293
 
184
294
  ```typescript
185
- import { ACPAuthClient } from '@artatol-acp/auth-nextjs/server';
295
+ import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware';
296
+ import { readFileSync } from 'fs';
186
297
 
187
- const client = new ACPAuthClient({
188
- baseUrl: process.env.ACP_AUTH_URL!,
189
- apiKey: process.env.ACP_AUTH_API_KEY!,
298
+ const publicKey = readFileSync('./keys/public.pem', 'utf-8');
299
+
300
+ export const middleware = createACPAuthMiddleware({
301
+ jwtPublicKey: publicKey,
302
+ publicPaths: ['/login', '/register', '/forgot-password'],
303
+ loginPath: '/login',
190
304
  });
191
305
 
192
- export async function POST(request: Request) {
193
- const { email, password } = await request.json();
306
+ export const config = {
307
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
308
+ };
309
+ ```
194
310
 
195
- const result = await client.login({ email, password });
311
+ ## Environment Variables
196
312
 
197
- return Response.json(result);
198
- }
313
+ ```env
314
+ ACP_AUTH_URL=https://sso.artatol.net
315
+ ACP_AUTH_API_KEY=your-api-key-here
199
316
  ```
200
317
 
201
- ## Password Requirements
318
+ ## API Reference
202
319
 
203
- Passwords must meet the following requirements:
204
- - Minimum 10 characters
205
- - At least one lowercase letter (a-z)
206
- - At least one uppercase letter (A-Z)
207
- - At least one number (0-9)
320
+ ### Server Functions (`@artatol-acp/auth-nextjs/server`)
321
+
322
+ | Function | Description |
323
+ |----------|-------------|
324
+ | `initACPAuth(options)` | Initialize auth configuration |
325
+ | `getUser()` | Get user from JWT (local, fast) → `{ id, email }` |
326
+ | `me()` | Get user from API (full data) → `{ id, email, twoFactorEnabled }` |
327
+ | `login(email, password)` | Login user, sets httpOnly cookies |
328
+ | `verify2FALogin(tempToken, code)` | Complete 2FA login |
329
+ | `logout()` | Logout user, clears cookies |
330
+ | `register(email, password)` | Register new user |
331
+ | `verifyEmail(token)` | Verify email address |
332
+ | `resendVerificationEmail(email)` | Resend verification email |
333
+ | `forgotPassword(email)` | Request password reset |
334
+ | `resetPassword(token, password)` | Reset password |
335
+ | `deleteAccount(password, confirmation)` | Delete account |
336
+ | `refreshAccessToken()` | Refresh access token |
337
+
338
+ ### Client Hook (`@artatol-acp/auth-nextjs/client`)
208
339
 
209
340
  ```typescript
210
- // Example validation on the client side
211
- function validatePassword(password: string): string[] {
212
- const errors: string[] = [];
213
-
214
- if (password.length < 10) {
215
- errors.push('Password must be at least 10 characters');
216
- }
217
- if (!/[a-z]/.test(password)) {
218
- errors.push('Password must contain at least one lowercase letter');
219
- }
220
- if (!/[A-Z]/.test(password)) {
221
- errors.push('Password must contain at least one uppercase letter');
222
- }
223
- if (!/[0-9]/.test(password)) {
224
- errors.push('Password must contain at least one number');
225
- }
226
-
227
- return errors;
228
- }
341
+ const {
342
+ user, // Current user or null
343
+ isLoading, // True during initial load
344
+ login, // (email, password) => Promise<{ requiresTwoFactor?, tempToken? }>
345
+ verify2FA, // (tempToken, code) => Promise<void>
346
+ logout, // () => Promise<void>
347
+ refresh, // () => Promise<boolean>
348
+ resendVerification, // (email) => Promise<void>
349
+ } = useAuth();
229
350
  ```
230
351
 
231
- ## Email Verification
232
-
233
- After registration, users must verify their email address before they can log in. The auth service automatically sends a verification email upon registration.
234
-
235
- ### Verification Flow
236
-
237
- 1. User registers → receives verification email
238
- 2. User clicks link in email → email is verified
239
- 3. User can now log in
240
-
241
- ### Server Actions
352
+ ### ACPAuthProvider Props
242
353
 
243
354
  ```typescript
244
- 'use server';
245
-
246
- import { verifyEmail, resendVerificationEmail } from '@artatol-acp/auth-nextjs/server';
247
- import { redirect } from 'next/navigation';
248
-
249
- export async function verifyEmailAction(token: string) {
250
- try {
251
- await verifyEmail(token);
252
- redirect('/login?verified=true');
253
- } catch (error) {
254
- redirect('/verification-error');
255
- }
256
- }
257
-
258
- export async function resendVerificationAction(email: string) {
259
- await resendVerificationEmail(email);
260
- // Always succeeds to prevent email enumeration
261
- return { message: 'If the email exists, a verification link has been sent' };
262
- }
355
+ <ACPAuthProvider
356
+ apiBasePath="/api/auth" // Optional, default: "/api/auth"
357
+ initialUser={user} // Optional, SSR user data
358
+ >
359
+ {children}
360
+ </ACPAuthProvider>
263
361
  ```
264
362
 
265
- ### Client Component Example
363
+ ### API Handlers (`@artatol-acp/auth-nextjs/handlers`)
266
364
 
267
365
  ```typescript
268
- 'use client';
269
-
270
- import { useAuth } from '@artatol-acp/auth-nextjs/client';
366
+ const handlers = createAuthHandlers({
367
+ baseUrl: string,
368
+ apiKey?: string,
369
+ cookies?: {
370
+ path?: string, // Default: "/"
371
+ secure?: boolean, // Default: NODE_ENV === "production"
372
+ sameSite?: 'strict' | 'lax' | 'none', // Default: "lax"
373
+ },
374
+ });
271
375
 
272
- export function ResendVerificationButton({ email }: { email: string }) {
273
- const { resendVerification } = useAuth();
274
- const [sent, setSent] = useState(false);
376
+ // Use combined handler
377
+ export const POST = handlers.authHandler;
275
378
 
276
- const handleResend = async () => {
277
- await resendVerification(email);
278
- setSent(true);
279
- };
280
-
281
- return (
282
- <button onClick={handleResend} disabled={sent}>
283
- {sent ? 'Email Sent' : 'Resend Verification Email'}
284
- </button>
285
- );
286
- }
379
+ // Or use individual handlers
380
+ export const POST = handlers.loginHandler;
287
381
  ```
288
382
 
289
- ### Handling Unverified Users
290
-
291
- When an unverified user tries to log in, they will receive an error:
383
+ ## Error Handling
292
384
 
293
385
  ```typescript
294
- export async function loginAction(formData: FormData) {
295
- const email = formData.get('email') as string;
296
- const password = formData.get('password') as string;
386
+ import { ACPAuthError } from '@artatol-acp/auth-nextjs/server';
297
387
 
298
- try {
299
- const result = await login(email, password);
300
-
301
- if ('requiresTwoFactor' in result) {
302
- return { requires2FA: true, tempToken: result.tempToken };
303
- }
304
-
305
- redirect('/dashboard');
306
- } catch (error: any) {
307
- if (error.message?.includes('Email not verified')) {
308
- return { error: 'Please verify your email before logging in' };
388
+ try {
389
+ await login(email, password);
390
+ } catch (error) {
391
+ if (error instanceof ACPAuthError) {
392
+ console.error('Message:', error.message);
393
+ console.error('Status:', error.statusCode);
394
+ console.error('Code:', error.code);
395
+
396
+ if (error.isAuthError()) {
397
+ // 401 - Invalid credentials
398
+ } else if (error.isValidationError()) {
399
+ // 400 - Validation error
400
+ } else if (error.isNetworkError()) {
401
+ // Network/connection error
309
402
  }
310
- throw error;
311
403
  }
312
404
  }
313
405
  ```
314
406
 
315
- ## Environment Variables
316
-
317
- ```env
318
- ACP_AUTH_URL=https://sso.artatol.com
319
- ACP_AUTH_API_KEY=your-api-key-here
320
- NEXT_PUBLIC_ACP_AUTH_URL=https://sso.artatol.com
321
- ```
322
-
323
- ## API Reference
324
-
325
- ### Server Functions
326
-
327
- - `initACPAuth(options)` - Initialize the auth client
328
- - `getUser()` - Get current user from JWT (local verification, returns `{ id, email }`)
329
- - `me()` - Get current user from API (returns full `User` with `twoFactorEnabled`)
330
- - `verifyAccessToken(token)` - Verify and decode access token (returns `{ id, email }`)
331
-
332
- ```typescript
333
- // getUser() returns JWTUser (fast, local)
334
- type JWTUser = {
335
- id: string;
336
- email: string;
337
- };
338
-
339
- // me() returns User (API call, complete data)
340
- type User = {
341
- id: string;
342
- email: string;
343
- twoFactorEnabled: boolean;
344
- };
345
- ```
346
- - `refreshAccessToken()` - Refresh access token using refresh token cookie
347
- - `login(email, password)` - Login user
348
- - `logout()` - Logout user
349
- - `register(email, password)` - Register new user (sends verification email)
350
- - `verifyEmail(token)` - Verify user's email address
351
- - `resendVerificationEmail(email)` - Resend verification email
352
- - `forgotPassword(email)` - Request password reset email
353
- - `resetPassword(token, newPassword)` - Reset password using token from email
354
- - `deleteAccount(password, confirmation)` - Delete authenticated user's account
355
-
356
- ### Client Hooks
357
-
358
- - `useAuth()` - Access auth context (user, isLoading, login, logout, refresh)
407
+ ### Common Error Codes
359
408
 
360
- ### Middleware
409
+ | Status | Meaning |
410
+ |--------|---------|
411
+ | 400 | Validation error (bad input) |
412
+ | 401 | Unauthorized (invalid credentials/token) |
413
+ | 403 | Forbidden (email not verified, account locked) |
414
+ | 429 | Too Many Requests (rate limited) |
361
415
 
362
- - `createACPAuthMiddleware(options)` - Create middleware for route protection
416
+ ## Password Requirements
363
417
 
364
- ## 2FA (Two-Factor Authentication)
418
+ - Minimum 10 characters
419
+ - At least one lowercase letter (a-z)
420
+ - At least one uppercase letter (A-Z)
421
+ - At least one number (0-9)
365
422
 
366
- ### Setup 2FA
423
+ ## 2FA Setup
367
424
 
368
425
  ```typescript
369
426
  'use server';
370
427
 
371
428
  import { ACPAuthClient } from '@artatol-acp/auth-nextjs/server';
429
+ import { cookies } from 'next/headers';
372
430
 
373
431
  export async function setup2FAAction(password: string) {
432
+ const cookieStore = await cookies();
433
+ const accessToken = cookieStore.get('access_token')?.value;
434
+
374
435
  const client = new ACPAuthClient({
375
436
  baseUrl: process.env.ACP_AUTH_URL!,
376
- apiKey: process.env.ACP_AUTH_API_KEY!,
437
+ apiKey: process.env.ACP_AUTH_API_KEY,
377
438
  });
378
439
 
379
- const accessToken = // ... get from session/cookie
380
-
381
- const { secret, qrCodeUrl, recoveryCodes } = await client.setup2FA(
440
+ const { qrCodeUrl, recoveryCodes } = await client.setup2FA(
382
441
  { password },
383
442
  accessToken
384
443
  );
385
444
 
386
445
  return { qrCodeUrl, recoveryCodes };
387
446
  }
388
- ```
389
447
 
390
- ### Verify 2FA Setup
448
+ export async function verify2FASetupAction(code: string) {
449
+ const cookieStore = await cookies();
450
+ const accessToken = cookieStore.get('access_token')?.value;
391
451
 
392
- ```typescript
393
- 'use server';
394
-
395
- export async function verify2FAAction(code: string) {
396
452
  const client = new ACPAuthClient({
397
453
  baseUrl: process.env.ACP_AUTH_URL!,
398
- apiKey: process.env.ACP_AUTH_API_KEY!,
454
+ apiKey: process.env.ACP_AUTH_API_KEY,
399
455
  });
400
456
 
401
- const accessToken = // ... get from session/cookie
402
-
403
457
  await client.verify2FA({ code }, accessToken);
404
458
  }
405
459
  ```
406
460
 
407
- ### Complete 2FA Login Flow
408
-
409
- ```typescript
410
- 'use server';
411
-
412
- import { login } from '@artatol-acp/auth-nextjs/server';
413
-
414
- export async function loginAction(email: string, password: string) {
415
- const result = await login(email, password);
416
-
417
- if ('requiresTwoFactor' in result) {
418
- // User has 2FA enabled
419
- return {
420
- requires2FA: true,
421
- tempToken: result.tempToken,
422
- };
423
- }
424
-
425
- // Regular login successful
426
- return { success: true };
427
- }
428
-
429
- export async function complete2FALogin(tempToken: string, code: string) {
430
- const client = new ACPAuthClient({
431
- baseUrl: process.env.ACP_AUTH_URL!,
432
- apiKey: process.env.ACP_AUTH_API_KEY!,
433
- });
434
-
435
- const result = await client.verify2FALogin({ tempToken, code });
436
- // Set cookies, etc.
437
- return result;
438
- }
439
- ```
440
-
441
- ### Disable 2FA
442
-
443
- ```typescript
444
- 'use server';
445
-
446
- export async function disable2FAAction(password: string, code: string) {
447
- const client = new ACPAuthClient({
448
- baseUrl: process.env.ACP_AUTH_URL!,
449
- apiKey: process.env.ACP_AUTH_API_KEY!,
450
- });
451
-
452
- const accessToken = // ... get from session/cookie
453
-
454
- await client.disable2FA({ password, code }, accessToken);
455
- }
456
- ```
457
-
458
- ## Password Reset
459
-
460
- ### Forgot Password
461
-
462
- Request a password reset email:
463
-
464
- ```typescript
465
- 'use server';
466
-
467
- import { forgotPassword } from '@artatol-acp/auth-nextjs/server';
468
-
469
- export async function forgotPasswordAction(formData: FormData) {
470
- const email = formData.get('email') as string;
471
-
472
- await forgotPassword(email);
473
- // Always succeeds to prevent email enumeration
474
- return { message: 'If the email exists, a reset link has been sent' };
475
- }
476
- ```
477
-
478
- ### Reset Password
479
-
480
- Reset password using the token from the email:
481
-
482
- ```typescript
483
- 'use server';
484
-
485
- import { resetPassword } from '@artatol-acp/auth-nextjs/server';
486
- import { redirect } from 'next/navigation';
487
-
488
- export async function resetPasswordAction(formData: FormData) {
489
- const token = formData.get('token') as string;
490
- const newPassword = formData.get('password') as string;
491
-
492
- try {
493
- await resetPassword(token, newPassword);
494
- redirect('/login?reset=success');
495
- } catch (error) {
496
- return { error: 'Invalid or expired reset token' };
497
- }
498
- }
499
- ```
500
-
501
- ### Client Component Example
502
-
503
- ```typescript
504
- 'use client';
505
-
506
- import { useState } from 'react';
507
- import { forgotPasswordAction } from './actions';
508
-
509
- export function ForgotPasswordForm() {
510
- const [submitted, setSubmitted] = useState(false);
511
-
512
- const handleSubmit = async (formData: FormData) => {
513
- await forgotPasswordAction(formData);
514
- setSubmitted(true);
515
- };
516
-
517
- if (submitted) {
518
- return <p>Check your email for a password reset link.</p>;
519
- }
520
-
521
- return (
522
- <form action={handleSubmit}>
523
- <input name="email" type="email" placeholder="Email" required />
524
- <button type="submit">Send Reset Link</button>
525
- </form>
526
- );
527
- }
528
- ```
529
-
530
- ## Delete Account
531
-
532
- Delete the authenticated user's account:
533
-
534
- ```typescript
535
- 'use server';
536
-
537
- import { deleteAccount } from '@artatol-acp/auth-nextjs/server';
538
- import { redirect } from 'next/navigation';
539
-
540
- export async function deleteAccountAction(formData: FormData) {
541
- const password = formData.get('password') as string;
542
- const confirmation = formData.get('confirmation') as string;
543
-
544
- try {
545
- await deleteAccount(password, confirmation);
546
- redirect('/goodbye');
547
- } catch (error) {
548
- return { error: 'Failed to delete account. Check your password.' };
549
- }
550
- }
551
- ```
552
-
553
- **Note:** The `confirmation` parameter must be the string `"DELETE"` to confirm account deletion.
554
-
555
- ## Health Check
556
-
557
- To check if the auth service is available:
558
-
559
- ```typescript
560
- import { ACPAuthClient } from '@artatol-acp/auth-nextjs/server';
561
-
562
- const client = new ACPAuthClient({
563
- baseUrl: process.env.ACP_AUTH_URL!,
564
- apiKey: process.env.ACP_AUTH_API_KEY!,
565
- });
566
-
567
- const health = await client.health();
568
- console.log(health); // { status: 'ok', timestamp: '...' }
569
- ```
570
-
571
- ## Automatic Token Refresh
572
-
573
- The Next.js SDK includes automatic token refresh functionality on the client side via the `ACPAuthProvider`.
574
-
575
- ### How It Works
576
-
577
- 1. When you use `ACPAuthProvider`, the SDK automatically manages access tokens
578
- 2. After successful login, a background interval starts
579
- 3. **Every 4 minutes**, the token is automatically refreshed (tokens expire after 5 minutes)
580
- 4. If refresh fails, the user is automatically logged out
581
- 5. The interval stops when the user logs out or the component unmounts
582
-
583
- ### Client-Side Usage
584
-
585
- ```typescript
586
- 'use client';
587
-
588
- import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client';
589
-
590
- export default function RootLayout({ children }) {
591
- return (
592
- <html>
593
- <body>
594
- <ACPAuthProvider
595
- baseUrl={process.env.NEXT_PUBLIC_ACP_AUTH_URL}
596
- >
597
- {children}
598
- </ACPAuthProvider>
599
- </body>
600
- </html>
601
- );
602
- }
603
- ```
604
-
605
- The `ACPAuthProvider` internally creates an `ACPAuthClient` with auto-refresh enabled:
606
- - `autoRefresh: true` (enabled by default)
607
- - `refreshThresholdSeconds: 60` (refreshes 60 seconds before token expires)
608
-
609
- ### Manual Token Management
610
-
611
- If you're using the client directly without the provider:
612
-
613
- ```typescript
614
- import { ACPAuthClient } from '@artatol-acp/auth-js';
615
-
616
- const client = new ACPAuthClient({
617
- baseUrl: 'https://sso.artatol.com',
618
- apiKey: '',
619
- autoRefresh: true,
620
- refreshThresholdSeconds: 60
621
- });
622
-
623
- // After login, token is automatically managed
624
- const result = await client.login({ email: '...', password: '...' });
625
-
626
- // Subsequent calls automatically refresh if needed
627
- const user = await client.me(); // No manual token management required
628
- ```
629
-
630
- ### Server-Side Token Refresh
631
-
632
- For server-side token refresh, use the `refreshAccessToken()` function:
633
-
634
- ```typescript
635
- 'use server';
636
-
637
- import { refreshAccessToken } from '@artatol-acp/auth-nextjs/server';
638
-
639
- export async function refreshTokenAction() {
640
- try {
641
- await refreshAccessToken();
642
- return { success: true };
643
- } catch (error) {
644
- return { success: false, error: 'Failed to refresh token' };
645
- }
646
- }
647
- ```
648
-
649
- ## Error Handling
650
-
651
- ```typescript
652
- import { ACPAuthError } from '@artatol-acp/auth-js';
653
-
654
- try {
655
- await login(email, password);
656
- } catch (error) {
657
- if (error instanceof ACPAuthError) {
658
- console.error('Auth error:', error.message);
659
- console.error('Status code:', error.statusCode);
660
-
661
- if (error.statusCode === 401) {
662
- // Invalid credentials
663
- } else if (error.statusCode === 429) {
664
- // Rate limited
665
- }
666
- } else {
667
- console.error('Unexpected error:', error);
668
- }
669
- }
670
- ```
671
-
672
- ### Common Error Codes
673
-
674
- | Status Code | Meaning |
675
- |-------------|---------|
676
- | 401 | Unauthorized (invalid credentials or token) |
677
- | 403 | Forbidden (email not verified, account locked) |
678
- | 429 | Too Many Requests (rate limited) |
679
- | 500 | Internal Server Error |
680
-
681
461
  ## License
682
462
 
683
463
  MIT