@zitadel/sdk-next 0.1.0-alpha.17 → 0.1.0-alpha.18
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 +112 -7
- package/dist/auth.d.ts +54 -13
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +152 -7
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +21 -10
- package/dist/context.d.ts +21 -10
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -8
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -24
- package/dist/jsx.d.ts +5 -0
- package/dist/lib/jwt.d.ts +9 -0
- package/dist/lib/jwt.d.ts.map +1 -0
- package/dist/lib/jwt.js +7 -0
- package/dist/middleware.d.ts +9 -10
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +294 -9
- package/dist/provider.d.ts +52 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +70 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +24 -0
- package/dist/server.d.ts +12 -2
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +10 -7
- package/dist/session.d.ts +61 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +115 -0
- package/dist/types.d.ts +8 -3
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -1
- package/dist/useAuth.d.ts +17 -5
- package/dist/useAuth.d.ts.map +1 -0
- package/dist/useAuth.js +18 -7
- package/package.json +24 -9
- package/dist/chunk-2BFQLJQE.js +0 -27
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-7D5LORRL.js +0 -201
- package/dist/chunk-KHTLEMQO.js +0 -8
- package/dist/chunk-WZU5OYIF.js +0 -41
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -136,6 +239,7 @@ export function LoginWidget() {
|
|
|
136
239
|
| `allowedTokenTypes` | `string[]` | `["JWT", "at+JWT"]` | Accepted `typ` header values (case-insensitive). Set to `[]` to disable this check |
|
|
137
240
|
| `clockSkewMs` | `number` | `5000` | Clock skew tolerance in ms for `exp`, `nbf`, `iat` |
|
|
138
241
|
| `jwksTimeoutMs` | `number` | `5000` | Timeout in ms for JWKS endpoint requests. Token is rejected if the fetch exceeds this window |
|
|
242
|
+
| `opaqueTokenTimeoutMs` | `number` | `5000` | Timeout in ms for opaque (non-JWT) session validation via `GET /sessions/me`. Also accepted by `auth()` |
|
|
139
243
|
| `audience` | `string \| string[]` | not validated | Expected `aud` claim value(s). When omitted, audience is not checked |
|
|
140
244
|
|
|
141
245
|
## How JWT verification works
|
|
@@ -144,9 +248,10 @@ export function LoginWidget() {
|
|
|
144
248
|
2. The JWT header is decoded to extract `kid` and `alg`
|
|
145
249
|
3. Tokens with an `alg` not in `allowedAlgorithms` (`RS256`, `ES256` by default) are rejected immediately — no JWKS fetch
|
|
146
250
|
4. Tokens with a `typ` not in `allowedTokenTypes` are rejected immediately
|
|
147
|
-
5. The public key is fetched from `{url}/
|
|
251
|
+
5. The public key is fetched from `{url}/auth/keys` (JWKS) using the Web Crypto API, with a 5 s timeout, and cached for 5 minutes per `kid`
|
|
148
252
|
6. The signature is verified **before** any claim checks
|
|
149
253
|
7. `iss` must be present and must equal `url` — tokens without an issuer are rejected
|
|
150
254
|
8. `exp` must be present and must be in the future (with `clockSkewMs` tolerance) — tokens without an expiry are rejected
|
|
151
255
|
9. `nbf` and `iat` are validated with `clockSkewMs` tolerance when present
|
|
152
256
|
10. The `x-nextgen-auth-token` header is stripped from all proxied requests to prevent internal state leakage
|
|
257
|
+
11. `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
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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/middleware';
|
|
|
20
59
|
* }
|
|
21
60
|
* ```
|
|
22
61
|
*
|
|
23
|
-
* @
|
|
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
|
-
|
|
3
|
-
} from "
|
|
4
|
-
import "./
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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,10 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
}):
|
|
9
|
-
declare function useAuthContext():
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "./
|
|
13
|
-
|
|
14
|
-
NextgenProvider,
|
|
15
|
-
useAuthContext
|
|
16
|
-
} from "./chunk-2BFQLJQE.js";
|
|
17
|
-
export {
|
|
18
|
-
NextgenProvider,
|
|
19
|
-
auth,
|
|
20
|
-
createProxy,
|
|
21
|
-
nextgenMiddleware,
|
|
22
|
-
useAuth,
|
|
23
|
-
useAuthContext
|
|
24
|
-
};
|
|
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 { auth } from "./auth.js";
|
|
11
|
+
export { NextgenProvider } from "./provider.js";
|
|
12
|
+
export { AuthContextProvider, useAuthContext } from "./context.js";
|
|
13
|
+
export { useAuth } from "./useAuth.js";
|
package/dist/jsx.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Types-only shim: scaffolded apps depend on @zitadel/sdk-next, not
|
|
2
|
+
// @zitadel/components, so the React JSX declarations for the <zitadel-*>
|
|
3
|
+
// elements are re-exposed here as `@zitadel/sdk-next/jsx`. Copied verbatim
|
|
4
|
+
// into dist/ by the build script.
|
|
5
|
+
/// <reference types="@zitadel/components/jsx" />
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports shared JWT verification from `@zitadel/sdk-core`.
|
|
3
|
+
*
|
|
4
|
+
* The JWT module is runtime-agnostic (uses `atob` which is available in both
|
|
5
|
+
* Edge and Node.js 16+) and is defined once in sdk-core.
|
|
6
|
+
*/
|
|
7
|
+
export { JWKS_TTL_MS, base64UrlDecode, decodeJwt, isJwtShaped, verifyJwt, } from "@zitadel/sdk-core/jwt";
|
|
8
|
+
export type { JwtPayload, JwtHeader, DecodedJwt, VerifyJwtOptions } from "@zitadel/sdk-core/jwt";
|
|
9
|
+
//# sourceMappingURL=jwt.d.ts.map
|