@zitadel/sdk-next 0.1.0-alpha.9 → 1.0.0-alpha.20

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 (45) hide show
  1. package/README.md +132 -24
  2. package/dist/auth.d.ts +54 -13
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +152 -7
  5. package/dist/client.d.ts +23 -2
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +21 -9
  8. package/dist/context.d.ts +21 -10
  9. package/dist/context.d.ts.map +1 -0
  10. package/dist/context.js +24 -8
  11. package/dist/index.d.ts +16 -9
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +13 -24
  14. package/dist/jsx.d.ts +5 -0
  15. package/dist/lib/jwt.d.ts +9 -0
  16. package/dist/lib/jwt.d.ts.map +1 -0
  17. package/dist/lib/jwt.js +7 -0
  18. package/dist/middleware.d.ts +9 -10
  19. package/dist/middleware.d.ts.map +1 -0
  20. package/dist/middleware.js +294 -9
  21. package/dist/provider.d.ts +52 -0
  22. package/dist/provider.d.ts.map +1 -0
  23. package/dist/provider.js +70 -0
  24. package/dist/react.d.ts +26 -0
  25. package/dist/react.d.ts.map +1 -0
  26. package/dist/react.js +24 -0
  27. package/dist/server.d.ts +12 -2
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +10 -7
  30. package/dist/session.d.ts +61 -0
  31. package/dist/session.d.ts.map +1 -0
  32. package/dist/session.js +115 -0
  33. package/dist/types.d.ts +8 -3
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +1 -1
  36. package/dist/useAuth.d.ts +17 -5
  37. package/dist/useAuth.d.ts.map +1 -0
  38. package/dist/useAuth.js +18 -7
  39. package/package.json +28 -13
  40. package/dist/chunk-2BFQLJQE.js +0 -27
  41. package/dist/chunk-4KENHIG4.js +0 -222
  42. package/dist/chunk-6F4PWJZI.js +0 -0
  43. package/dist/chunk-B7S6XMT3.js +0 -41
  44. package/dist/chunk-OCZMYSFX.js +0 -13
  45. package/dist/chunk-XTCHTAIQ.js +0 -12
package/README.md CHANGED
@@ -8,6 +8,23 @@ Next.js middleware and helpers for Nextgen Auth.
8
8
  pnpm add @zitadel/sdk-next
9
9
  ```
10
10
 
11
+ ## Entry points
12
+
13
+ | Import | Runs in | Provides |
14
+ | ------------------------------ | -------------------------------- | ------------------------------------------------ |
15
+ | `@zitadel/sdk-next/middleware` | Edge middleware | `nextgenMiddleware`, `createProxy` |
16
+ | `@zitadel/sdk-next/server` | Server Components, Route Handlers | `auth()`, `NextgenProvider` |
17
+ | `@zitadel/sdk-next/react` | Client Components | `useAuth()`, `AuthContextProvider` |
18
+ | `@zitadel/sdk-next/session` | Client Components | `getSession()` |
19
+ | `@zitadel/sdk-next/client` | Client boundary | Web-component registration, `configureZitadel()` |
20
+
21
+ The package root re-exports the server and client surfaces together for use
22
+ in server modules. Do not import the root from a `"use client"` module: it
23
+ pulls in the server-only `auth()`, which fails the build with an import trace
24
+ (exactly how depends on the bundler's tree shaking — the supported client
25
+ imports are `/react` and `/session`). `NextgenProvider` is itself server-only
26
+ because it accepts the token-bearing `auth()` result — see section 3.
27
+
11
28
  ## Setup
12
29
 
13
30
  ### 1. Middleware
@@ -40,7 +57,7 @@ The middleware runs on every matched route and does three things in one pass:
40
57
  ### 2. Reading auth in a Server Component
41
58
 
42
59
  ```ts
43
- import { auth } from "@zitadel/sdk-next";
60
+ import { auth } from "@zitadel/sdk-next/server";
44
61
 
45
62
  export default async function Page() {
46
63
  const session = await auth();
@@ -49,30 +66,65 @@ export default async function Page() {
49
66
  }
50
67
  ```
51
68
 
69
+ `auth()` reads the token the middleware tunnelled into the request headers
70
+ and **verifies it before trusting it** — JWTs cryptographically via JWKS,
71
+ opaque tokens against the backend's `GET /sessions/me` (which also supplies
72
+ the user's identity). A forged `x-nextgen-auth-token` header sent directly by
73
+ a client to a route outside the middleware `matcher` is rejected. Two things
74
+ follow:
75
+
76
+ - `auth()` only reports a session on routes the `matcher` covers — on other
77
+ routes the token never reaches it (see section 4 for chrome on public
78
+ pages).
79
+ - If the middleware runs with custom verification options (`audience`,
80
+ `allowedAlgorithms`, …), pass the same values to `auth()` so both layers
81
+ accept the same tokens.
82
+
83
+ `session.token` is the raw session token, available server-side for calling
84
+ upstream APIs. Never forward it into client components yourself —
85
+ `NextgenProvider` strips it for you (next section).
86
+
52
87
  ### 3. Reading auth in a Client Component
53
88
 
54
- Wrap your app in `NextgenProvider` (e.g. in your root layout):
89
+ Seed the client tree once in your root layout (a Server Component), then
90
+ read the state with `useAuth()` anywhere below it:
55
91
 
56
92
  ```tsx
57
- import { NextgenProvider } from '@zitadel/sdk-next';
93
+ import { auth, NextgenProvider } from '@zitadel/sdk-next/server';
58
94
 
59
95
  export default async function RootLayout({ children }) {
60
96
  const session = await auth();
61
97
  return (
62
98
  <html>
63
99
  <body>
64
- <NextgenProvider value={session}>{children}</NextgenProvider>
100
+ <NextgenProvider session={session}>{children}</NextgenProvider>
65
101
  </body>
66
102
  </html>
67
103
  );
68
104
  }
69
105
  ```
70
106
 
107
+ `NextgenProvider` converts the `auth()` result to the client-safe shape
108
+ **before** it crosses the server→client boundary: client components receive
109
+ `userId` / `email` / `name`, and the raw session token never enters the RSC
110
+ flight payload, where any script on the page could read it.
111
+
112
+ That strip only protects you while it runs on the server, which is why
113
+ `NextgenProvider` is server-only and **must not be re-exported through a
114
+ `"use client"` wrapper** (the common `providers.tsx` pattern): the wrapper
115
+ would become the client boundary, and its still-unstripped `session` prop —
116
+ token included — would serialise into the flight payload before the provider
117
+ ever ran. The `server-only` guard turns that wrapper into a build error
118
+ instead of a silent leak. To seed the context from client-side state (e.g. a
119
+ `getSession()` read), render `AuthContextProvider` from
120
+ `@zitadel/sdk-next/react` — it only accepts the token-less
121
+ `ClientAuthResult`.
122
+
71
123
  Then in any client component:
72
124
 
73
125
  ```tsx
74
126
  'use client';
75
- import { useAuth } from '@zitadel/sdk-next';
127
+ import { useAuth } from '@zitadel/sdk-next/react';
76
128
 
77
129
  export function UserBadge() {
78
130
  const auth = useAuth();
@@ -80,7 +132,58 @@ export function UserBadge() {
80
132
  }
81
133
  ```
82
134
 
83
- ### 4. Login page
135
+ `useAuth()` returns the same client-safe `ClientAuthResult` shape as
136
+ `getSession()` and sdk-nuxt's `useAuth()`. It reflects what the server knew
137
+ when the page rendered — which, like `auth()`, is only a live session on
138
+ routes the middleware `matcher` covers.
139
+
140
+ ### 4. Session state for your own UI (any page)
141
+
142
+ `auth()` only sees a session on routes the middleware `matcher` covers, and the
143
+ scaffolded matcher covers just the proxy path and the protected routes — so a
144
+ header on a public page would always look signed out. For your app's own chrome
145
+ (header navigation, account menus), read the session client-side with
146
+ `getSession()` from `@zitadel/sdk-next/session`. It fetches the same-origin
147
+ `{proxyPath}/sessions/me` — the same read the `<zitadel-session>` card performs —
148
+ so it works on every page and the answer is the server's:
149
+
150
+ ```tsx
151
+ 'use client';
152
+ import { useEffect, useState } from 'react';
153
+ import { getSession, type ClientAuthResult } from '@zitadel/sdk-next/session';
154
+
155
+ export function HeaderNav() {
156
+ // undefined = not yet known — render neutral chrome, not "Sign in".
157
+ const [auth, setAuth] = useState<ClientAuthResult>();
158
+ const [error, setError] = useState<Error>();
159
+ useEffect(() => {
160
+ getSession().then(setAuth, setError);
161
+ }, []);
162
+ if (error) return <span role="alert">Session unavailable</span>;
163
+ if (!auth) return null;
164
+ return auth.isAuthenticated ? (
165
+ <a href="/profile">{auth.session.name ?? auth.session.email ?? 'Account'}</a>
166
+ ) : (
167
+ <a href="/login">Sign in</a>
168
+ );
169
+ }
170
+ ```
171
+
172
+ A rejected `getSession()` means the state is *unknown* (broken proxy, network,
173
+ 5xx) — render a neutral or error state, never the signed-out CTAs.
174
+
175
+ A `200` with a non-empty `user_id` resolves to
176
+ `{ isAuthenticated: true, session: { userId, email, name } }` (client-safe —
177
+ no token); the canonical `401/auth.unauthorized`,
178
+ `404/sess.not_found`, and anonymous sessions resolve to signed out. The request
179
+ and response are both marked no-store. Any other response — including malformed
180
+ JSON or a framework's HTML 404 page from a misrouted proxy — throws so a broken
181
+ proxy doesn't silently render as signed out. Sign-in and sign-out navigate
182
+ (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page
183
+ load without extra wiring; to react in place, listen for the widgets'
184
+ `zitadel-signout` / `zitadel-flow-complete` events.
185
+
186
+ ### 5. Login page
84
187
 
85
188
  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
189
 
@@ -104,15 +207,17 @@ import dynamic from 'next/dynamic';
104
207
 
105
208
  const ZitadelLogin = dynamic(
106
209
  async () => {
107
- await import('@zitadel/components');
210
+ // The /client entry registers the custom elements AND re-exports
211
+ // configureZitadel, so an app that only declares @zitadel/sdk-next
212
+ // resolves everything it needs (strict package managers would not
213
+ // resolve @zitadel/api or @zitadel/components transitively).
214
+ const { configureZitadel } = await import('@zitadel/sdk-next/client');
215
+ configureZitadel({
216
+ projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID!,
217
+ proxyPath: '/__nextgen',
218
+ });
108
219
  return function ZitadelLoginElement() {
109
- return (
110
- <zitadel-login
111
- api-base="/__nextgen"
112
- project-id="demo"
113
- post-sign-in-url="/admin"
114
- />
115
- );
220
+ return <zitadel-login post-sign-in-url="/admin" />;
116
221
  };
117
222
  },
118
223
  { ssr: false },
@@ -123,6 +228,10 @@ export function LoginWidget() {
123
228
  }
124
229
  ```
125
230
 
231
+ There is no `api-base` attribute — the element reads the global handle from
232
+ `configureZitadel()` (or you can assign the returned handle to the element's
233
+ `project` property).
234
+
126
235
  ## Middleware options
127
236
 
128
237
  | Option | Type | Default | Description |
@@ -136,17 +245,16 @@ export function LoginWidget() {
136
245
  | `allowedTokenTypes` | `string[]` | `["JWT", "at+JWT"]` | Accepted `typ` header values (case-insensitive). Set to `[]` to disable this check |
137
246
  | `clockSkewMs` | `number` | `5000` | Clock skew tolerance in ms for `exp`, `nbf`, `iat` |
138
247
  | `jwksTimeoutMs` | `number` | `5000` | Timeout in ms for JWKS endpoint requests. Token is rejected if the fetch exceeds this window |
248
+ | `opaqueTokenTimeoutMs` | `number` | `5000` | Timeout in ms for opaque (non-JWT) session validation via `GET /sessions/me`. Also accepted by `auth()` |
249
+ | `proxyTimeoutMs` | `number` | `5000` | Timeout in ms for upstream proxy requests; requests exceeding it abort with a network error |
250
+ | `jwtKey` | `string` | unset | Reserved for future use — local PEM public key for offline JWT verification |
139
251
  | `audience` | `string \| string[]` | not validated | Expected `aud` claim value(s). When omitted, audience is not checked |
140
252
 
141
253
  ## How JWT verification works
142
254
 
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
255
+ The verification pipeline is shared across SDKs and documented once in
256
+ [`@zitadel/sdk-core`](https://github.com/zitadel/nextgen/tree/main/packages/sdk-core#how-jwt-verification-works).
257
+ On top of that pipeline, the Next.js middleware adds:
258
+
259
+ 1. The `x-nextgen-auth-token` header is stripped from all proxied requests to prevent internal state leakage
260
+ 2. `auth()` re-applies the same verification to the tunnelled token in the server runtime — the header alone is never treated as proof of a session
package/dist/auth.d.ts CHANGED
@@ -1,17 +1,56 @@
1
- import { AuthResult } from '@zitadel/sdk-core/types';
2
-
1
+ import "server-only";
2
+ import type { AuthResult, NextgenMiddlewareOptions } from "./types.js";
3
+ /**
4
+ * Options for {@link auth}.
5
+ *
6
+ * These mirror the token-verification subset of the middleware options. When
7
+ * the middleware runs with custom verification settings (a non-default
8
+ * `audience`, `allowedAlgorithms`, …), pass the same values here — otherwise
9
+ * a token the middleware accepted can fail re-verification in `auth()` and
10
+ * render as signed out.
11
+ */
12
+ export type AuthOptions = Pick<NextgenMiddlewareOptions, "url" | "allowedAlgorithms" | "clockSkewMs" | "audience" | "allowedTokenTypes" | "jwksTimeoutMs" | "opaqueTokenTimeoutMs">;
3
13
  /**
4
14
  * Reads the auth state in a React Server Component or Next.js Route Handler.
5
15
  *
6
- * - **JWT tokens** are decoded locally (no backend round-trip).
7
- * - **Opaque encrypted tokens** have already been validated by the
8
- * middleware via `GET /sessions/me`. If the `x-nextgen-auth-token`
9
- * header is set, the session is authentic. Session details (email,
10
- * name) are not available server-side for opaque tokens use the
11
- * `/__nextgen/sessions/me` proxy from a client component to fetch them.
16
+ * The session token arrives on the `x-nextgen-auth-token` request header,
17
+ * tunnelled there by the middleware and `auth()` **verifies it before
18
+ * trusting it**:
19
+ *
20
+ * - **JWT tokens** are verified cryptographically (signature via JWKS,
21
+ * issuer, expiry, algorithm and type allow-lists) using the same rules and
22
+ * defaults as the middleware. The JWKS is cached in-process, so this does
23
+ * not add a backend round-trip after the first call.
24
+ * - **Opaque encrypted tokens** are validated against the backend's
25
+ * `GET /sessions/me`, which also supplies the user's identity (`userId`,
26
+ * `email`, `name`). The lookup is deduplicated per render pass via React
27
+ * `cache()`.
28
+ *
29
+ * The header alone is never proof of anything: on routes the middleware
30
+ * `matcher` does not cover, the middleware cannot neutralise a forged
31
+ * client-supplied header, so `auth()` re-verifies every value it reads.
32
+ * A header value that fails verification is treated as signed out.
33
+ *
34
+ * **Matcher precondition (read this):** the middleware only tunnels the
35
+ * session token on routes covered by its `matcher`. On uncovered routes the
36
+ * cookie never reaches `auth()`, so `auth()` reports signed out even when a
37
+ * live session exists. Either extend the `matcher` to every route that calls
38
+ * `auth()`, or — for app chrome like headers and account menus — read the
39
+ * session client-side with `getSession()` from `@zitadel/sdk-next/session`,
40
+ * which works on any page.
41
+ *
42
+ * The backend URL comes from `options.url`, falling back to the same
43
+ * `ZITADEL_URL` environment variable the middleware uses. If the middleware
44
+ * runs with custom verification options (`audience`, `allowedAlgorithms`, …),
45
+ * pass the same values here; see {@link AuthOptions}.
46
+ *
47
+ * This module is server-only: importing it (or the package root) from a
48
+ * client component fails at build time. Client components read auth state
49
+ * from `@zitadel/sdk-next/react` (`useAuth()`) or
50
+ * `@zitadel/sdk-next/session` (`getSession()`).
12
51
  *
13
52
  * ```ts
14
- * import { auth } from "@zitadel/sdk-next";
53
+ * import { auth } from "@zitadel/sdk-next/server";
15
54
  *
16
55
  * export default async function Page() {
17
56
  * const session = await auth();
@@ -20,8 +59,10 @@ import { AuthResult } from '@zitadel/sdk-core/types';
20
59
  * }
21
60
  * ```
22
61
  *
23
- * @returns The current {@link AuthResult}.
62
+ * @param options - Optional verification settings; see {@link AuthOptions}.
63
+ * @returns The current {@link AuthResult}. `session.token` is the raw session
64
+ * token for calling upstream APIs server-side — never forward it into
65
+ * client components (the `NextgenProvider` strips it for you).
24
66
  */
25
- declare function auth(): Promise<AuthResult>;
26
-
27
- export { auth };
67
+ export declare function auth(options?: AuthOptions): Promise<AuthResult>;
68
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,UAAU,EAAiB,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAItF;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,wBAAwB,EACtB,KAAK,GACL,mBAAmB,GACnB,aAAa,GACb,UAAU,GACV,mBAAmB,GACnB,eAAe,GACf,sBAAsB,CACzB,CAAC;AA8DF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6DzE"}
package/dist/auth.js CHANGED
@@ -1,7 +1,152 @@
1
- import {
2
- auth
3
- } from "./chunk-B7S6XMT3.js";
4
- import "./chunk-OCZMYSFX.js";
5
- export {
6
- auth
7
- };
1
+ import "server-only";
2
+ import { headers } from "next/headers";
3
+ import { cache } from "react";
4
+ import { isJwtShaped, verifyJwt } from "./lib/jwt.js";
5
+ /**
6
+ * Validates an opaque (non-JWT) session token against the backend's
7
+ * `GET /sessions/me` and returns the client-safe identity, or `null` when the
8
+ * backend does not confirm a signed-in user.
9
+ *
10
+ * Wrapped in React's `cache()` so that many components calling `auth()` in
11
+ * the same render pass share a single backend round-trip. Arguments are
12
+ * primitives on purpose — `cache()` memoises by argument identity.
13
+ *
14
+ * Fail-closed: network errors, timeouts, and unexpected statuses all resolve
15
+ * to `null` (signed out). Unexpected failures are logged so a broken proxy or
16
+ * unreachable backend is distinguishable from a genuinely missing session.
17
+ */
18
+ const validateOpaqueSession = cache(async (token, issuerUrl, timeoutMs) => {
19
+ try {
20
+ const res = await fetch(`${issuerUrl}/sessions/me`, {
21
+ method: "GET",
22
+ headers: { accept: "application/json", cookie: `__nextgen_session=${token}` },
23
+ signal: AbortSignal.timeout(timeoutMs),
24
+ });
25
+ // 401 = no/invalid session token, 404 = session gone (revoked/expired):
26
+ // both are the server's definitive "not signed in".
27
+ if (res.status === 401 || res.status === 404) {
28
+ return null;
29
+ }
30
+ if (!res.ok) {
31
+ console.warn(`[nextgen] auth(): session validation failed with HTTP ${res.status} from ` +
32
+ `${issuerUrl}/sessions/me — treating as signed out. If this persists, the ` +
33
+ `backend URL is likely misconfigured (ZITADEL_URL or the \`url\` option).`);
34
+ return null;
35
+ }
36
+ const session = (await res.json());
37
+ // An anonymous session (no verified user factor yet) has no user_id —
38
+ // for server-rendered UI that is "not signed in".
39
+ if (!session.user_id) {
40
+ return null;
41
+ }
42
+ return {
43
+ userId: session.user_id,
44
+ email: session.email ?? null,
45
+ name: session.name ?? null,
46
+ };
47
+ }
48
+ catch {
49
+ console.warn(`[nextgen] auth(): could not reach ${issuerUrl}/sessions/me to validate the ` +
50
+ `session token — treating as signed out.`);
51
+ return null;
52
+ }
53
+ });
54
+ /**
55
+ * Reads the auth state in a React Server Component or Next.js Route Handler.
56
+ *
57
+ * The session token arrives on the `x-nextgen-auth-token` request header,
58
+ * tunnelled there by the middleware — and `auth()` **verifies it before
59
+ * trusting it**:
60
+ *
61
+ * - **JWT tokens** are verified cryptographically (signature via JWKS,
62
+ * issuer, expiry, algorithm and type allow-lists) using the same rules and
63
+ * defaults as the middleware. The JWKS is cached in-process, so this does
64
+ * not add a backend round-trip after the first call.
65
+ * - **Opaque encrypted tokens** are validated against the backend's
66
+ * `GET /sessions/me`, which also supplies the user's identity (`userId`,
67
+ * `email`, `name`). The lookup is deduplicated per render pass via React
68
+ * `cache()`.
69
+ *
70
+ * The header alone is never proof of anything: on routes the middleware
71
+ * `matcher` does not cover, the middleware cannot neutralise a forged
72
+ * client-supplied header, so `auth()` re-verifies every value it reads.
73
+ * A header value that fails verification is treated as signed out.
74
+ *
75
+ * **Matcher precondition (read this):** the middleware only tunnels the
76
+ * session token on routes covered by its `matcher`. On uncovered routes the
77
+ * cookie never reaches `auth()`, so `auth()` reports signed out even when a
78
+ * live session exists. Either extend the `matcher` to every route that calls
79
+ * `auth()`, or — for app chrome like headers and account menus — read the
80
+ * session client-side with `getSession()` from `@zitadel/sdk-next/session`,
81
+ * which works on any page.
82
+ *
83
+ * The backend URL comes from `options.url`, falling back to the same
84
+ * `ZITADEL_URL` environment variable the middleware uses. If the middleware
85
+ * runs with custom verification options (`audience`, `allowedAlgorithms`, …),
86
+ * pass the same values here; see {@link AuthOptions}.
87
+ *
88
+ * This module is server-only: importing it (or the package root) from a
89
+ * client component fails at build time. Client components read auth state
90
+ * from `@zitadel/sdk-next/react` (`useAuth()`) or
91
+ * `@zitadel/sdk-next/session` (`getSession()`).
92
+ *
93
+ * ```ts
94
+ * import { auth } from "@zitadel/sdk-next/server";
95
+ *
96
+ * export default async function Page() {
97
+ * const session = await auth();
98
+ * if (!session.isAuthenticated) return <p>Not signed in</p>;
99
+ * return <p>Hello {session.session.userId}</p>;
100
+ * }
101
+ * ```
102
+ *
103
+ * @param options - Optional verification settings; see {@link AuthOptions}.
104
+ * @returns The current {@link AuthResult}. `session.token` is the raw session
105
+ * token for calling upstream APIs server-side — never forward it into
106
+ * client components (the `NextgenProvider` strips it for you).
107
+ */
108
+ export async function auth(options = {}) {
109
+ const headerStore = await headers();
110
+ const token = headerStore.get("x-nextgen-auth-token");
111
+ if (!token) {
112
+ return { isAuthenticated: false, session: null };
113
+ }
114
+ const { url = process.env.ZITADEL_URL ?? "http://localhost:8080", allowedAlgorithms = ["RS256", "ES256"], clockSkewMs = 5000, audience, allowedTokenTypes = ["JWT", "at+JWT"], jwksTimeoutMs, opaqueTokenTimeoutMs = 5000, } = options;
115
+ if (isJwtShaped(token)) {
116
+ const payload = await verifyJwt(token, {
117
+ issuerUrl: url,
118
+ allowedAlgorithms,
119
+ clockSkewMs,
120
+ audience,
121
+ allowedTokenTypes,
122
+ jwksTimeoutMs,
123
+ });
124
+ if (payload?.sub) {
125
+ return {
126
+ isAuthenticated: true,
127
+ session: {
128
+ userId: payload.sub,
129
+ email: payload.email ?? null,
130
+ name: payload.name ?? null,
131
+ token,
132
+ },
133
+ };
134
+ }
135
+ // A middleware-tunnelled JWT always verifies (the middleware applied the
136
+ // same checks moments earlier). Reaching this branch means the header was
137
+ // NOT vetted by the middleware — a route outside the `matcher` received a
138
+ // client-forged header — or the JWKS endpoint is unreachable from this
139
+ // runtime, or auth() runs with verification options that diverge from the
140
+ // middleware's.
141
+ console.warn("[nextgen] auth(): rejected an x-nextgen-auth-token header that failed JWT " +
142
+ "verification. If this route is not covered by the middleware matcher, this was " +
143
+ "a forged client header; otherwise check that auth() and the middleware use the " +
144
+ `same verification options and that the JWKS endpoint at ${url}/auth/keys is reachable.`);
145
+ return { isAuthenticated: false, session: null };
146
+ }
147
+ const session = await validateOpaqueSession(token, url, opaqueTokenTimeoutMs);
148
+ if (session) {
149
+ return { isAuthenticated: true, session: { ...session, token } };
150
+ }
151
+ return { isAuthenticated: false, session: null };
152
+ }
package/dist/client.d.ts CHANGED
@@ -1,2 +1,23 @@
1
- export { ZitadelLogin, ZitadelLogout } from '@zitadel/components';
2
- export { ZitadelConfig, ZitadelProject, configureZitadel, getApi } from '@zitadel/api/config';
1
+ /**
2
+ * Re-exports the `<zitadel-login>` and `<zitadel-logout>` Lit web components
3
+ * for use in Next.js apps.
4
+ *
5
+ * Import this inside a `"use client"` boundary (e.g. a dynamic import
6
+ * with `{ ssr: false }`) to register the custom elements with the
7
+ * browser's global registry:
8
+ *
9
+ * ```ts
10
+ * await import("@zitadel/sdk-next/client");
11
+ * ```
12
+ *
13
+ * SDK configuration is done via `configureZitadel()`, re-exported here so
14
+ * a consuming app that only declares `@zitadel/sdk-next` as a direct
15
+ * dependency can configure the SDK without reaching into
16
+ * `@zitadel/api/config` (which strict package managers would not
17
+ * resolve). Call it inside the same `"use client"` boundary before the
18
+ * components mount.
19
+ */
20
+ export { ZitadelLogin, ZitadelLogout, ZitadelSession, businessLocales } from "@zitadel/components";
21
+ export { configureZitadel, getApi } from "@zitadel/api/config";
22
+ export type { ZitadelConfig, ZitadelProject } from "@zitadel/api/config";
23
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/client.js CHANGED
@@ -1,9 +1,21 @@
1
- // src/client.ts
2
- import { ZitadelLogin, ZitadelLogout } from "@zitadel/components";
3
- import { configureZitadel, getApi } from "@zitadel/api/config";
4
- export {
5
- ZitadelLogin,
6
- ZitadelLogout,
7
- configureZitadel,
8
- getApi
9
- };
1
+ /**
2
+ * Re-exports the `<zitadel-login>` and `<zitadel-logout>` Lit web components
3
+ * for use in Next.js apps.
4
+ *
5
+ * Import this inside a `"use client"` boundary (e.g. a dynamic import
6
+ * with `{ ssr: false }`) to register the custom elements with the
7
+ * browser's global registry:
8
+ *
9
+ * ```ts
10
+ * await import("@zitadel/sdk-next/client");
11
+ * ```
12
+ *
13
+ * SDK configuration is done via `configureZitadel()`, re-exported here so
14
+ * a consuming app that only declares `@zitadel/sdk-next` as a direct
15
+ * dependency can configure the SDK without reaching into
16
+ * `@zitadel/api/config` (which strict package managers would not
17
+ * resolve). Call it inside the same `"use client"` boundary before the
18
+ * components mount.
19
+ */
20
+ export { ZitadelLogin, ZitadelLogout, ZitadelSession, businessLocales } from "@zitadel/components";
21
+ export { configureZitadel, getApi } from "@zitadel/api/config";
package/dist/context.d.ts CHANGED
@@ -1,11 +1,22 @@
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;
1
+ import { type ReactNode } from "react";
2
+ import type { ClientAuthResult } from "./types.js";
3
+ /**
4
+ * Client-side context carrier. In the standard server-seeded setup, render
5
+ * {@link NextgenProvider} from `@zitadel/sdk-next/server` instead — it
6
+ * normalises the server's `auth()` result to the client-safe shape (dropping
7
+ * the raw session token) *before* the value crosses the server→client
8
+ * component boundary, and its `server-only` guard keeps it out of client
9
+ * wrappers where that strip would come too late.
10
+ *
11
+ * Render this component directly only from client code that already holds a
12
+ * client-safe value — e.g. seeding from a `getSession()` read.
13
+ *
14
+ * The `value` prop is deliberately typed as {@link ClientAuthResult}: the
15
+ * raw session token must never enter client-side state.
16
+ */
17
+ export declare function AuthContextProvider({ value, children, }: {
18
+ value: ClientAuthResult;
7
19
  children: ReactNode;
8
- }): react_jsx_runtime.JSX.Element;
9
- declare function useAuthContext(): AuthResult;
10
-
11
- export { NextgenProvider, useAuthContext };
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ export declare function useAuthContext(): ClientAuthResult;
22
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAEA;AAED,wBAAgB,cAAc,IAAI,gBAAgB,CAEjD"}
package/dist/context.js CHANGED
@@ -1,9 +1,25 @@
1
1
  "use client";
2
- import {
3
- NextgenProvider,
4
- useAuthContext
5
- } from "./chunk-2BFQLJQE.js";
6
- export {
7
- NextgenProvider,
8
- useAuthContext
9
- };
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const defaultValue = { isAuthenticated: false, session: null };
5
+ const NextgenAuthContext = createContext(defaultValue);
6
+ /**
7
+ * Client-side context carrier. In the standard server-seeded setup, render
8
+ * {@link NextgenProvider} from `@zitadel/sdk-next/server` instead — it
9
+ * normalises the server's `auth()` result to the client-safe shape (dropping
10
+ * the raw session token) *before* the value crosses the server→client
11
+ * component boundary, and its `server-only` guard keeps it out of client
12
+ * wrappers where that strip would come too late.
13
+ *
14
+ * Render this component directly only from client code that already holds a
15
+ * client-safe value — e.g. seeding from a `getSession()` read.
16
+ *
17
+ * The `value` prop is deliberately typed as {@link ClientAuthResult}: the
18
+ * raw session token must never enter client-side state.
19
+ */
20
+ export function AuthContextProvider({ value, children, }) {
21
+ return _jsx(NextgenAuthContext.Provider, { value: value, children: children });
22
+ }
23
+ export function useAuthContext() {
24
+ return useContext(NextgenAuthContext);
25
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,16 @@
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';
1
+ /**
2
+ * Package root for **Server Components and server modules only**: it
3
+ * includes the server-only `auth()`, so importing it from a `"use client"`
4
+ * module fails at build time. Client components import from
5
+ * `@zitadel/sdk-next/react` (provider + hooks) or
6
+ * `@zitadel/sdk-next/session` (`getSession()`) instead.
7
+ */
8
+ export * from "./types.js";
9
+ export { nextgenMiddleware, createProxy } from "./middleware.js";
10
+ export type { ProxyOptions, ProxyHandler } from "./middleware.js";
11
+ export { auth } from "./auth.js";
12
+ export type { AuthOptions } from "./auth.js";
13
+ export { NextgenProvider } from "./provider.js";
14
+ export { AuthContextProvider, useAuthContext } from "./context.js";
15
+ export { useAuth } from "./useAuth.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}