@artatol-acp/auth-nextjs 0.5.4 → 0.5.6
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 +28 -9
- package/dist/proxy.d.ts +16 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +117 -0
- package/dist/proxy.js.map +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -297,27 +297,46 @@ export function LoginForm() {
|
|
|
297
297
|
}
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
-
## Middleware (
|
|
300
|
+
## Middleware (REQUIRED)
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
> **⚠️ IMPORTANT:** Middleware is **required** for automatic token refresh to work. Without it, users will be logged out when their access token expires (every 5 minutes).
|
|
303
|
+
|
|
304
|
+
The middleware handles:
|
|
305
|
+
- Automatic access token refresh using the refresh token
|
|
306
|
+
- Route protection (redirect to login if not authenticated)
|
|
307
|
+
- Token validation
|
|
308
|
+
|
|
309
|
+
Create `middleware.ts` in your project root:
|
|
303
310
|
|
|
304
311
|
```typescript
|
|
305
312
|
import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware';
|
|
306
|
-
import { readFileSync } from 'fs';
|
|
307
313
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
publicPaths: ['/login', '/register', '/forgot-password'],
|
|
314
|
+
export default createACPAuthMiddleware({
|
|
315
|
+
baseUrl: process.env.ACP_AUTH_URL!,
|
|
316
|
+
jwtPublicKey: process.env.ACP_AUTH_JWT_PUBLIC_KEY!,
|
|
317
|
+
// Optional: customize public paths (defaults shown below)
|
|
318
|
+
publicPaths: ['/login', '/register', '/forgot-password', '/reset-password', '/verify-email', '/verify-2fa'],
|
|
313
319
|
loginPath: '/login',
|
|
320
|
+
// Optional: cookie configuration for SSO
|
|
321
|
+
cookies: {
|
|
322
|
+
domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
|
|
323
|
+
},
|
|
314
324
|
});
|
|
315
325
|
|
|
316
326
|
export const config = {
|
|
317
|
-
matcher: ['/((?!
|
|
327
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
318
328
|
};
|
|
319
329
|
```
|
|
320
330
|
|
|
331
|
+
### Why is middleware required?
|
|
332
|
+
|
|
333
|
+
In Next.js App Router, cookies can only be modified in:
|
|
334
|
+
- Route Handlers (`app/api/...`)
|
|
335
|
+
- Server Actions
|
|
336
|
+
- Middleware
|
|
337
|
+
|
|
338
|
+
Server Components (pages) **cannot** set cookies. When a user's access token expires and they navigate to a page, the SDK cannot refresh the token from within the page render. The middleware intercepts the request **before** the page renders and handles the refresh.
|
|
339
|
+
|
|
321
340
|
## Environment Variables
|
|
322
341
|
|
|
323
342
|
```env
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
export type ACPAuthMiddlewareOptions = {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
jwtPublicKey: string;
|
|
6
|
+
publicPaths?: string[];
|
|
7
|
+
loginPath?: string;
|
|
8
|
+
cookies?: {
|
|
9
|
+
domain?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
secure?: boolean;
|
|
12
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function createACPAuthMiddleware(options: ACPAuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
16
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;KACtC,CAAC;CACH,CAAC;AAIF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,IAiB/B,SAAS,WAAW,oCA4F7D"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { jwtVerify, importSPKI } from 'jose';
|
|
3
|
+
let publicKey = null;
|
|
4
|
+
export function createACPAuthMiddleware(options) {
|
|
5
|
+
const { baseUrl, apiKey, jwtPublicKey, publicPaths = ['/login', '/register', '/forgot-password', '/reset-password', '/verify-email', '/verify-2fa'], loginPath = '/login', cookies: cookieConfig = {}, } = options;
|
|
6
|
+
const { domain: cookieDomain, path: cookiePath = '/', secure = process.env.NODE_ENV === 'production', sameSite = 'lax', } = cookieConfig;
|
|
7
|
+
return async function acpAuthMiddleware(request) {
|
|
8
|
+
const { pathname } = request.nextUrl;
|
|
9
|
+
// Allow public paths
|
|
10
|
+
if (publicPaths.some((path) => pathname.startsWith(path))) {
|
|
11
|
+
return NextResponse.next();
|
|
12
|
+
}
|
|
13
|
+
// Allow API routes (they handle their own auth)
|
|
14
|
+
if (pathname.startsWith('/api/')) {
|
|
15
|
+
return NextResponse.next();
|
|
16
|
+
}
|
|
17
|
+
// Check for access token
|
|
18
|
+
const accessToken = request.cookies.get('access_token')?.value;
|
|
19
|
+
const refreshToken = request.cookies.get('refresh_token')?.value;
|
|
20
|
+
// If no access token, try to refresh
|
|
21
|
+
if (!accessToken) {
|
|
22
|
+
if (!refreshToken) {
|
|
23
|
+
// No tokens at all, redirect to login
|
|
24
|
+
const url = request.nextUrl.clone();
|
|
25
|
+
url.pathname = loginPath;
|
|
26
|
+
url.searchParams.set('from', pathname);
|
|
27
|
+
return NextResponse.redirect(url);
|
|
28
|
+
}
|
|
29
|
+
// Try to refresh
|
|
30
|
+
const newAccessToken = await tryRefresh(baseUrl, apiKey, refreshToken);
|
|
31
|
+
if (!newAccessToken) {
|
|
32
|
+
// Refresh failed, redirect to login
|
|
33
|
+
const url = request.nextUrl.clone();
|
|
34
|
+
url.pathname = loginPath;
|
|
35
|
+
url.searchParams.set('from', pathname);
|
|
36
|
+
return NextResponse.redirect(url);
|
|
37
|
+
}
|
|
38
|
+
// Set new access token cookie and continue
|
|
39
|
+
const response = NextResponse.next();
|
|
40
|
+
response.cookies.set('access_token', newAccessToken, {
|
|
41
|
+
httpOnly: true,
|
|
42
|
+
secure,
|
|
43
|
+
sameSite,
|
|
44
|
+
maxAge: 60 * 5, // 5 minutes
|
|
45
|
+
path: cookiePath,
|
|
46
|
+
...(cookieDomain && { domain: cookieDomain }),
|
|
47
|
+
});
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
// Verify access token
|
|
51
|
+
try {
|
|
52
|
+
if (!publicKey) {
|
|
53
|
+
publicKey = await importSPKI(jwtPublicKey, 'EdDSA');
|
|
54
|
+
}
|
|
55
|
+
await jwtVerify(accessToken, publicKey, {
|
|
56
|
+
algorithms: ['EdDSA'],
|
|
57
|
+
});
|
|
58
|
+
return NextResponse.next();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Token invalid or expired, try to refresh
|
|
62
|
+
if (!refreshToken) {
|
|
63
|
+
const url = request.nextUrl.clone();
|
|
64
|
+
url.pathname = loginPath;
|
|
65
|
+
url.searchParams.set('from', pathname);
|
|
66
|
+
return NextResponse.redirect(url);
|
|
67
|
+
}
|
|
68
|
+
const newAccessToken = await tryRefresh(baseUrl, apiKey, refreshToken);
|
|
69
|
+
if (!newAccessToken) {
|
|
70
|
+
// Refresh failed, redirect to login
|
|
71
|
+
const url = request.nextUrl.clone();
|
|
72
|
+
url.pathname = loginPath;
|
|
73
|
+
url.searchParams.set('from', pathname);
|
|
74
|
+
return NextResponse.redirect(url);
|
|
75
|
+
}
|
|
76
|
+
// Set new access token cookie and continue
|
|
77
|
+
const response = NextResponse.next();
|
|
78
|
+
response.cookies.set('access_token', newAccessToken, {
|
|
79
|
+
httpOnly: true,
|
|
80
|
+
secure,
|
|
81
|
+
sameSite,
|
|
82
|
+
maxAge: 60 * 5, // 5 minutes
|
|
83
|
+
path: cookiePath,
|
|
84
|
+
...(cookieDomain && { domain: cookieDomain }),
|
|
85
|
+
});
|
|
86
|
+
return response;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function tryRefresh(baseUrl, apiKey, refreshToken) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${baseUrl}/refresh`, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
...(apiKey ? { 'X-API-Key': apiKey } : {}),
|
|
97
|
+
Cookie: `refresh_token=${refreshToken}`,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
console.error('[ACP Auth Middleware] Refresh failed:', response.status);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
if (!data.success || !data.data?.accessToken) {
|
|
106
|
+
console.error('[ACP Auth Middleware] Invalid refresh response');
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
console.log('[ACP Auth Middleware] Token refreshed successfully');
|
|
110
|
+
return data.data.accessToken;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error('[ACP Auth Middleware] Refresh error:', error);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
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.6",
|
|
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",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./middleware": {
|
|
26
26
|
"import": "./dist/middleware.js",
|
|
27
27
|
"types": "./dist/middleware.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./proxy": {
|
|
30
|
+
"import": "./dist/proxy.js",
|
|
31
|
+
"types": "./dist/proxy.d.ts"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"files": [
|