@zitadel/sdk-next 0.0.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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.js +7 -0
- package/dist/chunk-2BFQLJQE.js +27 -0
- package/dist/chunk-5P5THDJF.js +12 -0
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-EAUJMJ45.js +34 -0
- package/dist/chunk-UTJFJPDR.js +177 -0
- package/dist/chunk-XTCHTAIQ.js +12 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +9 -0
- package/dist/context.d.ts +11 -0
- package/dist/context.js +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +24 -0
- package/dist/middleware.d.ts +80 -0
- package/dist/middleware.js +9 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +1 -0
- package/dist/useAuth.d.ts +5 -0
- package/dist/useAuth.js +8 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZITADEL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# @zitadel/sdk-next
|
|
2
|
+
|
|
3
|
+
Next.js middleware and helpers for Nextgen Auth.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @zitadel/sdk-next
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Middleware
|
|
14
|
+
|
|
15
|
+
Create `src/proxy.ts` at the root of your Next.js app:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { nextgenMiddleware } from '@zitadel/sdk-next/middleware';
|
|
19
|
+
import type { NextRequest } from 'next/server';
|
|
20
|
+
|
|
21
|
+
export function proxy(req: NextRequest) {
|
|
22
|
+
return nextgenMiddleware(req, {
|
|
23
|
+
url: process.env.ZITADEL_URL,
|
|
24
|
+
protectedRoutes: ['/admin', '/dashboard*'],
|
|
25
|
+
loginPath: '/login',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const config = {
|
|
30
|
+
matcher: ['/__nextgen/:path*', '/admin', '/login'],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The middleware runs on every matched route and does three things in one pass:
|
|
35
|
+
|
|
36
|
+
1. **Proxies** `/__nextgen/*` requests to the auth backend
|
|
37
|
+
2. **Verifies** the session JWT via JWKS using the Web Crypto API
|
|
38
|
+
3. **Redirects** unauthenticated requests to `loginPath` for protected routes
|
|
39
|
+
|
|
40
|
+
### 2. Reading auth in a Server Component
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { auth } from "@zitadel/sdk-next";
|
|
44
|
+
|
|
45
|
+
export default async function Page() {
|
|
46
|
+
const session = await auth();
|
|
47
|
+
if (!session.isAuthenticated) return <p>Not signed in</p>;
|
|
48
|
+
return <p>Hello {session.session.email}</p>;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Reading auth in a Client Component
|
|
53
|
+
|
|
54
|
+
Wrap your app in `NextgenProvider` (e.g. in your root layout):
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { NextgenProvider } from '@zitadel/sdk-next';
|
|
58
|
+
|
|
59
|
+
export default async function RootLayout({ children }) {
|
|
60
|
+
const session = await auth();
|
|
61
|
+
return (
|
|
62
|
+
<html>
|
|
63
|
+
<body>
|
|
64
|
+
<NextgenProvider value={session}>{children}</NextgenProvider>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then in any client component:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
'use client';
|
|
75
|
+
import { useAuth } from '@zitadel/sdk-next';
|
|
76
|
+
|
|
77
|
+
export function UserBadge() {
|
|
78
|
+
const auth = useAuth();
|
|
79
|
+
return <span>{auth.isAuthenticated ? auth.session.email : 'Guest'}</span>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Login page
|
|
84
|
+
|
|
85
|
+
The `<zitadel-login>` web component (from `@zitadel/components`) must be rendered client-side only. Split it into a server wrapper and a client widget:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// app/login/page.tsx (server)
|
|
89
|
+
import { auth } from '@zitadel/sdk-next';
|
|
90
|
+
import { redirect } from 'next/navigation';
|
|
91
|
+
import { LoginWidget } from './widget';
|
|
92
|
+
|
|
93
|
+
export default async function LoginPage() {
|
|
94
|
+
const session = await auth();
|
|
95
|
+
if (session.isAuthenticated) redirect('/admin');
|
|
96
|
+
return <LoginWidget />;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// app/login/widget.tsx (client)
|
|
102
|
+
'use client';
|
|
103
|
+
import dynamic from 'next/dynamic';
|
|
104
|
+
|
|
105
|
+
const ZitadelLogin = dynamic(
|
|
106
|
+
async () => {
|
|
107
|
+
await import('@zitadel/components');
|
|
108
|
+
return function ZitadelLoginElement() {
|
|
109
|
+
return (
|
|
110
|
+
<zitadel-login
|
|
111
|
+
api-base="/__nextgen"
|
|
112
|
+
project-id="demo"
|
|
113
|
+
post-sign-in-url="/admin"
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
{ ssr: false },
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
export function LoginWidget() {
|
|
122
|
+
return <ZitadelLogin />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Middleware options
|
|
127
|
+
|
|
128
|
+
| Option | Type | Default | Description |
|
|
129
|
+
| ------------------- | -------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `url` | `string` | `ZITADEL_URL` env | Full URL of the Zitadel auth backend |
|
|
131
|
+
| `proxyPath` | `string` | `"/__nextgen"` | Path prefix proxied to the auth backend |
|
|
132
|
+
| `protectedRoutes` | `string[]` | `[]` | Paths requiring a valid session. Trailing `*` matches sub-paths |
|
|
133
|
+
| `ignoredRoutes` | `string[]` | `[]` | Paths skipped entirely — no JWT check, no tunnelling. Useful for webhooks or health checks. Trailing `*` matches sub-paths |
|
|
134
|
+
| `loginPath` | `string` | `"/login"` | Where to redirect unauthenticated users |
|
|
135
|
+
| `allowedAlgorithms` | `string[]` | `["RS256", "ES256"]` | JWT `alg` values to accept. Tokens with any other algorithm are rejected before JWKS is fetched |
|
|
136
|
+
| `allowedTokenTypes` | `string[]` | `["JWT", "at+JWT"]` | Accepted `typ` header values (case-insensitive). Set to `[]` to disable this check |
|
|
137
|
+
| `clockSkewMs` | `number` | `5000` | Clock skew tolerance in ms for `exp`, `nbf`, `iat` |
|
|
138
|
+
| `jwksTimeoutMs` | `number` | `5000` | Timeout in ms for JWKS endpoint requests. Token is rejected if the fetch exceeds this window |
|
|
139
|
+
| `audience` | `string \| string[]` | not validated | Expected `aud` claim value(s). When omitted, audience is not checked |
|
|
140
|
+
|
|
141
|
+
## How JWT verification works
|
|
142
|
+
|
|
143
|
+
1. Bearer token from `Authorization` header is checked first; `__nextgen_session` cookie is the fallback
|
|
144
|
+
2. The JWT header is decoded to extract `kid` and `alg`
|
|
145
|
+
3. Tokens with an `alg` not in `allowedAlgorithms` (`RS256`, `ES256` by default) are rejected immediately — no JWKS fetch
|
|
146
|
+
4. Tokens with a `typ` not in `allowedTokenTypes` are rejected immediately
|
|
147
|
+
5. The public key is fetched from `{url}/oauth/v2/keys` (JWKS) using the Web Crypto API, with a 5 s timeout, and cached for 5 minutes per `kid`
|
|
148
|
+
6. The signature is verified **before** any claim checks
|
|
149
|
+
7. `iss` must be present and must equal `url` — tokens without an issuer are rejected
|
|
150
|
+
8. `exp` must be present and must be in the future (with `clockSkewMs` tolerance) — tokens without an expiry are rejected
|
|
151
|
+
9. `nbf` and `iat` are validated with `clockSkewMs` tolerance when present
|
|
152
|
+
10. The `x-nextgen-auth-token` header is stripped from all proxied requests to prevent internal state leakage
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AuthResult } from '@zitadel/sdk-core/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads the auth state in a React Server Component or Next.js Route Handler.
|
|
5
|
+
*
|
|
6
|
+
* The middleware verifies the JWT and tunnels it to the RSC runtime via
|
|
7
|
+
* `x-nextgen-auth-token`. This function decodes the tunnelled token without
|
|
8
|
+
* re-verifying the signature — verification has already been done by the
|
|
9
|
+
* middleware on every request.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { auth } from "@zitadel/sdk-next";
|
|
13
|
+
*
|
|
14
|
+
* export default async function Page() {
|
|
15
|
+
* const session = await auth();
|
|
16
|
+
* if (!session.isAuthenticated) return <p>Not signed in</p>;
|
|
17
|
+
* return <p>Hello {session.session.email}</p>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @returns The current {@link AuthResult}.
|
|
22
|
+
*/
|
|
23
|
+
declare function auth(): Promise<AuthResult>;
|
|
24
|
+
|
|
25
|
+
export { auth };
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/context.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var defaultValue = { isAuthenticated: false, session: null };
|
|
5
|
+
var NextgenAuthContext = createContext(defaultValue);
|
|
6
|
+
function NextgenProvider({
|
|
7
|
+
session,
|
|
8
|
+
children
|
|
9
|
+
}) {
|
|
10
|
+
let value;
|
|
11
|
+
if (!session) {
|
|
12
|
+
value = { isAuthenticated: false, session: null };
|
|
13
|
+
} else if ("isAuthenticated" in session) {
|
|
14
|
+
value = session;
|
|
15
|
+
} else {
|
|
16
|
+
value = { isAuthenticated: true, session };
|
|
17
|
+
}
|
|
18
|
+
return /* @__PURE__ */ jsx(NextgenAuthContext.Provider, { value, children });
|
|
19
|
+
}
|
|
20
|
+
function useAuthContext() {
|
|
21
|
+
return useContext(NextgenAuthContext);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
NextgenProvider,
|
|
26
|
+
useAuthContext
|
|
27
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeJwt
|
|
3
|
+
} from "./chunk-5P5THDJF.js";
|
|
4
|
+
|
|
5
|
+
// src/auth.ts
|
|
6
|
+
import { headers } from "next/headers";
|
|
7
|
+
async function auth() {
|
|
8
|
+
try {
|
|
9
|
+
const headerStore = await headers();
|
|
10
|
+
const token = headerStore.get("x-nextgen-auth-token");
|
|
11
|
+
if (!token) {
|
|
12
|
+
return { isAuthenticated: false, session: null };
|
|
13
|
+
}
|
|
14
|
+
const { payload } = decodeJwt(token);
|
|
15
|
+
if (!payload.sub) {
|
|
16
|
+
return { isAuthenticated: false, session: null };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
isAuthenticated: true,
|
|
20
|
+
session: {
|
|
21
|
+
userId: payload.sub,
|
|
22
|
+
email: payload.email ?? null,
|
|
23
|
+
name: payload.name ?? null,
|
|
24
|
+
token
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
} catch {
|
|
28
|
+
return { isAuthenticated: false, session: null };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
auth
|
|
34
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
verifyJwt
|
|
3
|
+
} from "./chunk-5P5THDJF.js";
|
|
4
|
+
|
|
5
|
+
// src/middleware.ts
|
|
6
|
+
import {
|
|
7
|
+
HOP_BY_HOP,
|
|
8
|
+
INTERNAL_HEADERS,
|
|
9
|
+
filterResponseHeaders,
|
|
10
|
+
matchesRoutes
|
|
11
|
+
} from "@zitadel/sdk-core/middleware";
|
|
12
|
+
import { NextResponse } from "next/server";
|
|
13
|
+
function tunnelHeaders(req, extra) {
|
|
14
|
+
const headers = new Headers(req.headers);
|
|
15
|
+
headers.delete("x-middleware-override-headers");
|
|
16
|
+
const injectedNames = [];
|
|
17
|
+
for (const [name, value] of Object.entries(extra)) {
|
|
18
|
+
headers.set(name, value);
|
|
19
|
+
headers.set(`x-middleware-request-${name}`, value);
|
|
20
|
+
injectedNames.push(name);
|
|
21
|
+
}
|
|
22
|
+
headers.set("x-middleware-override-headers", injectedNames.join(","));
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
async function nextgenMiddleware(req, options = {}) {
|
|
26
|
+
const {
|
|
27
|
+
url = process.env.ZITADEL_URL ?? "http://localhost:8080",
|
|
28
|
+
proxyPath = "/__nextgen",
|
|
29
|
+
protectedRoutes = [],
|
|
30
|
+
ignoredRoutes = [],
|
|
31
|
+
loginPath = "/login",
|
|
32
|
+
allowedAlgorithms = ["RS256", "ES256"],
|
|
33
|
+
clockSkewMs = 5e3,
|
|
34
|
+
audience,
|
|
35
|
+
allowedTokenTypes = ["JWT", "at+JWT"],
|
|
36
|
+
jwksTimeoutMs,
|
|
37
|
+
proxyTimeoutMs = 5e3,
|
|
38
|
+
onExchangeResponse
|
|
39
|
+
} = options;
|
|
40
|
+
if (!loginPath.startsWith("/") || loginPath.startsWith("//")) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`[nextgen] loginPath must be a relative path starting with a single "/". Received: "${loginPath}". Using an absolute or protocol-relative URL would allow open-redirect attacks.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const { pathname } = new URL(req.url);
|
|
46
|
+
if (matchesRoutes(pathname, ignoredRoutes)) {
|
|
47
|
+
const headers = tunnelHeaders(req, { "x-nextgen-auth-token": "" });
|
|
48
|
+
return NextResponse.next({ request: { headers } });
|
|
49
|
+
}
|
|
50
|
+
if (pathname === proxyPath || pathname.startsWith(`${proxyPath}/`)) {
|
|
51
|
+
return proxyRequest(
|
|
52
|
+
req,
|
|
53
|
+
url,
|
|
54
|
+
proxyPath,
|
|
55
|
+
proxyTimeoutMs,
|
|
56
|
+
onExchangeResponse
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return handleAuth(req, {
|
|
60
|
+
url,
|
|
61
|
+
protectedRoutes,
|
|
62
|
+
loginPath,
|
|
63
|
+
allowedAlgorithms,
|
|
64
|
+
clockSkewMs,
|
|
65
|
+
audience,
|
|
66
|
+
allowedTokenTypes,
|
|
67
|
+
jwksTimeoutMs,
|
|
68
|
+
pathname
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function proxyRequest(req, authUrl, proxyPath, proxyTimeoutMs, onExchangeResponse) {
|
|
72
|
+
const url = new URL(req.url);
|
|
73
|
+
const suffix = url.pathname.slice(proxyPath.length);
|
|
74
|
+
const target = `${authUrl}${suffix}${url.search}`;
|
|
75
|
+
const upstreamHeaders = new Headers();
|
|
76
|
+
for (const [key, value] of req.headers.entries()) {
|
|
77
|
+
const lower = key.toLowerCase();
|
|
78
|
+
if (!HOP_BY_HOP.has(lower) && !INTERNAL_HEADERS.has(lower)) {
|
|
79
|
+
upstreamHeaders.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const directIp = req.ip ?? req.headers.get("x-real-ip");
|
|
83
|
+
if (directIp) {
|
|
84
|
+
const existingXff = upstreamHeaders.get("x-forwarded-for");
|
|
85
|
+
upstreamHeaders.set(
|
|
86
|
+
"x-forwarded-for",
|
|
87
|
+
existingXff ? `${existingXff}, ${directIp}` : directIp
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (!upstreamHeaders.has("x-forwarded-host")) {
|
|
91
|
+
upstreamHeaders.set("x-forwarded-host", url.host);
|
|
92
|
+
}
|
|
93
|
+
if (!upstreamHeaders.has("x-forwarded-proto")) {
|
|
94
|
+
upstreamHeaders.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
95
|
+
}
|
|
96
|
+
const hasBody = !["GET", "HEAD"].includes(req.method);
|
|
97
|
+
const upstream = await fetch(target, {
|
|
98
|
+
method: req.method,
|
|
99
|
+
headers: upstreamHeaders,
|
|
100
|
+
body: hasBody ? req.body : void 0,
|
|
101
|
+
redirect: "manual",
|
|
102
|
+
signal: AbortSignal.timeout(proxyTimeoutMs),
|
|
103
|
+
...hasBody ? { duplex: "half" } : {}
|
|
104
|
+
});
|
|
105
|
+
const responseHeaders = filterResponseHeaders(upstream.headers);
|
|
106
|
+
const setCookies = upstream.headers.getSetCookie?.() ?? [];
|
|
107
|
+
for (const cookie of setCookies) {
|
|
108
|
+
responseHeaders.append("set-cookie", cookie);
|
|
109
|
+
}
|
|
110
|
+
let response = new Response(upstream.body, {
|
|
111
|
+
status: upstream.status,
|
|
112
|
+
headers: responseHeaders
|
|
113
|
+
});
|
|
114
|
+
const isExchange = req.method === "POST" && suffix.startsWith("/sessions/exchange");
|
|
115
|
+
if (isExchange && onExchangeResponse) {
|
|
116
|
+
response = await onExchangeResponse(response);
|
|
117
|
+
}
|
|
118
|
+
return response;
|
|
119
|
+
}
|
|
120
|
+
async function handleAuth(req, opts) {
|
|
121
|
+
const {
|
|
122
|
+
url,
|
|
123
|
+
protectedRoutes,
|
|
124
|
+
loginPath,
|
|
125
|
+
allowedAlgorithms,
|
|
126
|
+
clockSkewMs,
|
|
127
|
+
audience,
|
|
128
|
+
allowedTokenTypes,
|
|
129
|
+
jwksTimeoutMs,
|
|
130
|
+
pathname
|
|
131
|
+
} = opts;
|
|
132
|
+
const authHeader = req.headers.get("authorization");
|
|
133
|
+
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
134
|
+
const cookieToken = req.cookies.get("__nextgen_session")?.value ?? null;
|
|
135
|
+
const token = bearerToken ?? cookieToken;
|
|
136
|
+
const payload = token ? await verifyJwt(token, {
|
|
137
|
+
issuerUrl: url,
|
|
138
|
+
allowedAlgorithms,
|
|
139
|
+
clockSkewMs,
|
|
140
|
+
audience,
|
|
141
|
+
allowedTokenTypes,
|
|
142
|
+
jwksTimeoutMs
|
|
143
|
+
}) : null;
|
|
144
|
+
if (payload && token && payload.sub) {
|
|
145
|
+
const tunnelled2 = tunnelHeaders(req, { "x-nextgen-auth-token": token });
|
|
146
|
+
return NextResponse.next({ request: { headers: tunnelled2 } });
|
|
147
|
+
}
|
|
148
|
+
const tunnelled = tunnelHeaders(req, { "x-nextgen-auth-token": "" });
|
|
149
|
+
const staleNextgenCookies = req.cookies.getAll().filter((c) => c.name.startsWith("__nextgen"));
|
|
150
|
+
if (matchesRoutes(pathname, protectedRoutes)) {
|
|
151
|
+
const loginUrl = new URL(loginPath, req.url);
|
|
152
|
+
loginUrl.searchParams.set("next", pathname);
|
|
153
|
+
const redirect = NextResponse.redirect(loginUrl, { status: 302 });
|
|
154
|
+
for (const cookie of staleNextgenCookies) {
|
|
155
|
+
redirect.cookies.delete(cookie.name);
|
|
156
|
+
}
|
|
157
|
+
return redirect;
|
|
158
|
+
}
|
|
159
|
+
const response = NextResponse.next({ request: { headers: tunnelled } });
|
|
160
|
+
for (const cookie of staleNextgenCookies) {
|
|
161
|
+
response.cookies.delete(cookie.name);
|
|
162
|
+
}
|
|
163
|
+
return response;
|
|
164
|
+
}
|
|
165
|
+
function createProxy(config, options = {}) {
|
|
166
|
+
const mergedOptions = {
|
|
167
|
+
...options,
|
|
168
|
+
proxyPath: config.proxyPath,
|
|
169
|
+
url: config.url
|
|
170
|
+
};
|
|
171
|
+
return (req) => nextgenMiddleware(req, mergedOptions);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
nextgenMiddleware,
|
|
176
|
+
createProxy
|
|
177
|
+
};
|
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { AuthResult, NextgenSession } from '@zitadel/sdk-core/types';
|
|
4
|
+
|
|
5
|
+
declare function NextgenProvider({ session, children, }: {
|
|
6
|
+
session: AuthResult | NextgenSession | null;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}): react_jsx_runtime.JSX.Element;
|
|
9
|
+
declare function useAuthContext(): AuthResult;
|
|
10
|
+
|
|
11
|
+
export { NextgenProvider, useAuthContext };
|
package/dist/context.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AuthResult, AuthState, NextgenMiddlewareOptions, NextgenSession, UnauthState } from '@zitadel/sdk-core/types';
|
|
2
|
+
export { ProxyHandler, ProxyOptions, createProxy, nextgenMiddleware } from './middleware.js';
|
|
3
|
+
export { auth } from './auth.js';
|
|
4
|
+
export { NextgenProvider, useAuthContext } from './context.js';
|
|
5
|
+
export { useAuth } from './useAuth.js';
|
|
6
|
+
import '@zitadel/api/config';
|
|
7
|
+
import 'next/server';
|
|
8
|
+
import 'react/jsx-runtime';
|
|
9
|
+
import 'react';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProxy,
|
|
3
|
+
nextgenMiddleware
|
|
4
|
+
} from "./chunk-UTJFJPDR.js";
|
|
5
|
+
import {
|
|
6
|
+
auth
|
|
7
|
+
} from "./chunk-EAUJMJ45.js";
|
|
8
|
+
import "./chunk-5P5THDJF.js";
|
|
9
|
+
import "./chunk-6F4PWJZI.js";
|
|
10
|
+
import {
|
|
11
|
+
useAuth
|
|
12
|
+
} from "./chunk-XTCHTAIQ.js";
|
|
13
|
+
import {
|
|
14
|
+
NextgenProvider,
|
|
15
|
+
useAuthContext
|
|
16
|
+
} from "./chunk-2BFQLJQE.js";
|
|
17
|
+
export {
|
|
18
|
+
NextgenProvider,
|
|
19
|
+
auth,
|
|
20
|
+
createProxy,
|
|
21
|
+
nextgenMiddleware,
|
|
22
|
+
useAuth,
|
|
23
|
+
useAuthContext
|
|
24
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ZitadelProject } from '@zitadel/api/config';
|
|
2
|
+
import { NextgenMiddlewareOptions } from '@zitadel/sdk-core/types';
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Next.js Edge middleware that handles proxying, JWT verification, and route
|
|
7
|
+
* protection in a single pass.
|
|
8
|
+
*
|
|
9
|
+
* Place this in your `middleware.ts` file:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { nextgenMiddleware } from "@zitadel/sdk-next/middleware";
|
|
13
|
+
* import type { NextRequest } from "next/server";
|
|
14
|
+
*
|
|
15
|
+
* export function middleware(req: NextRequest) {
|
|
16
|
+
* return nextgenMiddleware(req, {
|
|
17
|
+
* url: process.env.ZITADEL_URL,
|
|
18
|
+
* protectedRoutes: ["/profile"],
|
|
19
|
+
* loginPath: "/login",
|
|
20
|
+
* });
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* export const config = {
|
|
24
|
+
* matcher: ["/__nextgen/:path*", "/profile/:path*"],
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param req - The incoming Next.js edge request.
|
|
29
|
+
* @param options - Middleware configuration options.
|
|
30
|
+
* @returns A `NextResponse` or `Response` to continue, redirect, or proxy.
|
|
31
|
+
*/
|
|
32
|
+
declare function nextgenMiddleware(req: NextRequest, options?: NextgenMiddlewareOptions): Promise<NextResponse | Response>;
|
|
33
|
+
/**
|
|
34
|
+
* Options for {@link createProxy} that are separate from the shared
|
|
35
|
+
* {@link ZitadelConfig}. These configure route protection, login
|
|
36
|
+
* redirects, and JWT verification behaviour.
|
|
37
|
+
*
|
|
38
|
+
* `proxyPath` and `url` are omitted because they come from the
|
|
39
|
+
* {@link ZitadelConfig} passed as the first argument.
|
|
40
|
+
*/
|
|
41
|
+
type ProxyOptions = Omit<NextgenMiddlewareOptions, 'proxyPath' | 'url'>;
|
|
42
|
+
/**
|
|
43
|
+
* A pre-configured middleware handler returned by {@link createProxy}.
|
|
44
|
+
*/
|
|
45
|
+
type ProxyHandler = (req: NextRequest) => Promise<NextResponse | Response>;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a pre-configured middleware handler from the SDK config
|
|
48
|
+
* returned by `configureZitadel()`. This is the derived-service
|
|
49
|
+
* derived service pattern:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* // src/zitadel.ts
|
|
53
|
+
* import { configureZitadel } from "@zitadel/api/config";
|
|
54
|
+
* import { createProxy } from "@zitadel/sdk-next/middleware";
|
|
55
|
+
*
|
|
56
|
+
* const zitadel = configureZitadel({
|
|
57
|
+
* projectId: "demo",
|
|
58
|
+
* url: process.env.ZITADEL_URL,
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* export const proxy = createProxy(zitadel, {
|
|
62
|
+
* protectedRoutes: ["/admin*"],
|
|
63
|
+
* loginPath: "/login",
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* Then in middleware.ts:
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { proxy } from "./zitadel";
|
|
71
|
+
* export const middleware = proxy;
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @param config - The SDK handle from `configureZitadel()`.
|
|
75
|
+
* @param options - Route protection and JWT options.
|
|
76
|
+
* @returns A middleware handler function.
|
|
77
|
+
*/
|
|
78
|
+
declare function createProxy(config: ZitadelProject, options?: ProxyOptions): ProxyHandler;
|
|
79
|
+
|
|
80
|
+
export { type ProxyHandler, type ProxyOptions, createProxy, nextgenMiddleware };
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":true,"importHelpers":true,"module":99,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUncheckedIndexedAccess":true,"noUnusedLocals":true,"skipLibCheck":true,"strict":true,"target":9,"tsBuildInfoFile":"./tsconfig.tsbuildinfo"},"version":"5.9.3"}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./chunk-6F4PWJZI.js";
|
package/dist/useAuth.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zitadel/sdk-next",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Next.js helpers and mock auth UI for Zitadel",
|
|
5
|
+
"homepage": "https://github.com/zitadel/nextgen/tree/main/packages/sdk-next#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/zitadel/nextgen/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/zitadel/nextgen.git",
|
|
13
|
+
"directory": "packages/sdk-next"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./server": {
|
|
25
|
+
"types": "./dist/server.d.ts",
|
|
26
|
+
"import": "./dist/server.js"
|
|
27
|
+
},
|
|
28
|
+
"./client": {
|
|
29
|
+
"types": "./dist/client.d.ts",
|
|
30
|
+
"import": "./dist/client.js"
|
|
31
|
+
},
|
|
32
|
+
"./middleware": {
|
|
33
|
+
"types": "./dist/middleware.d.ts",
|
|
34
|
+
"import": "./dist/middleware.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"server-only": "^0.0.1",
|
|
42
|
+
"@zitadel/api": "0.0.0",
|
|
43
|
+
"@zitadel/sdk-core": "0.0.0",
|
|
44
|
+
"@zitadel/components": "0.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"next": ">=14",
|
|
48
|
+
"react": ">=18",
|
|
49
|
+
"react-dom": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^9.0.0",
|
|
53
|
+
"tsup": "^8.3.5",
|
|
54
|
+
"typescript": "^5.7.3",
|
|
55
|
+
"@eslint/json": "^0.11.0",
|
|
56
|
+
"@eslint/markdown": "^6.0.0",
|
|
57
|
+
"@testing-library/react": "^16.0.0",
|
|
58
|
+
"@testing-library/user-event": "^14.0.0",
|
|
59
|
+
"@types/react": "^19.2.14",
|
|
60
|
+
"eslint": "^9.0.0",
|
|
61
|
+
"eslint-config-prettier": "^10.0.0",
|
|
62
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
|
63
|
+
"eslint-plugin-import": "^2.31.0",
|
|
64
|
+
"eslint-plugin-jsx-a11y": "^6.10.0",
|
|
65
|
+
"eslint-plugin-perfectionist": "^4.0.0",
|
|
66
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
67
|
+
"eslint-plugin-react": "^7.37.0",
|
|
68
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
69
|
+
"eslint-plugin-testing-library": "^7.0.0",
|
|
70
|
+
"jsdom": "^26.0.0",
|
|
71
|
+
"next": "^16.2.4",
|
|
72
|
+
"prettier": "^3.0.0",
|
|
73
|
+
"typescript-eslint": "^8.0.0",
|
|
74
|
+
"vitest": "^3.0.0"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsup src/middleware.ts src/auth.ts src/context.tsx src/useAuth.ts src/types.ts src/index.ts src/server.ts src/client.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
|
|
78
|
+
"typecheck": "tsc --noEmit",
|
|
79
|
+
"test": "vitest run --passWithNoTests",
|
|
80
|
+
"lint": "eslint ."
|
|
81
|
+
}
|
|
82
|
+
}
|