@zitadel/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: zitadel-sdk-nuxt
3
+ description: >-
4
+ Integrate Zitadel authentication into an existing or custom Nuxt app with
5
+ @zitadel/sdk-nuxt. Use when the user wants login, logout, session handling,
6
+ or protected routes in a Nuxt 4 app and is wiring the SDK by hand rather than
7
+ letting the CLI scaffold a fresh project — registering the Nuxt module,
8
+ reading auth state, or rendering the `<zitadel-login>` / `<zitadel-logout>`
9
+ components.
10
+ ---
11
+
12
+ # @zitadel/sdk-nuxt
13
+
14
+ A Nuxt module, Nitro middleware, and composables that add Zitadel (Nextgen)
15
+ auth to a Nuxt 4 app. The module proxies auth traffic same-origin under
16
+ `/__nextgen`, verifies the session JWT via JWKS on the server, seeds auth state
17
+ into SSR and hydrates it on the client, and gates protected routes.
18
+
19
+ ## When to use
20
+
21
+ The `zitadel` CLI (`npx @zitadel/cli@latest setup --framework nuxt`) scaffolds the module,
22
+ plugin, login page, and `.env.local` into a fresh or detected Nuxt project —
23
+ prefer it for greenfield work. Reach for **this skill** when you are adding auth
24
+ to an **existing or custom** Nuxt app by hand, adjusting a scaffolded wiring, or
25
+ wiring the middleware without the module.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pnpm add @zitadel/sdk-nuxt@latest
31
+ ```
32
+
33
+ Peer dependencies: `nuxt >=4`, `h3 >=1`.
34
+
35
+ ## Integrate
36
+
37
+ The recommended path is the Nuxt module. Register it under the `nextgen` key:
38
+
39
+ ```ts
40
+ // nuxt.config.ts
41
+ export default defineNuxtConfig({
42
+ modules: ['@zitadel/sdk-nuxt/module'],
43
+ nextgen: {
44
+ protectedRoutes: ['/admin', '/dashboard*'], // trailing * matches sub-paths
45
+ loginPath: '/login',
46
+ },
47
+ });
48
+ ```
49
+
50
+ The module registers the Nitro middleware and auth plugin and auto-imports the
51
+ composables — no manual `server/middleware/auth.ts`. It reads `ZITADEL_URL`,
52
+ `NUXT_PUBLIC_ZITADEL_PROJECT_ID`, and the server-only `ZITADEL_PROJECT_SECRET`
53
+ from the environment, or in `nuxt dev` from `.env.local` when the environment
54
+ has none (Nuxt itself loads only `.env`). `url` and the project secret default to `ZITADEL_URL` and
55
+ `ZITADEL_PROJECT_SECRET`; pass `nextgen.url` only to override. A built server
56
+ reads `ZITADEL_URL` and `ZITADEL_PROJECT_SECRET` from its own runtime environment (`NUXT_NEXTGEN_URL`
57
+ and `NUXT_NEXTGEN_PROJECT_SECRET` take precedence) and never has them baked
58
+ into `.output/`. A `nextgen.url` or `runtimeConfig.public.zitadelProjectId` in
59
+ `nuxt.config.ts` wins over the variables. Sessions count only in the app's own
60
+ project, the public project id unless `nextgen.projectId` names one: with none,
61
+ nobody is signed in.
62
+
63
+ The module also loads the web components in the browser through a client-only
64
+ plugin and provides the business copy overlay as
65
+ `useNuxtApp().$zitadelBusinessLocales`. Do not install or import
66
+ `@zitadel/components` in the app.
67
+
68
+ Login and logout pages bind the project handle from `useZitadelProject()` inside
69
+ `<ClientOnly>`; navigation after each flow is driven by the URL props (there is
70
+ no `api-base` attribute):
71
+
72
+ ```vue
73
+ <script setup lang="ts">
74
+ const project = useZitadelProject();
75
+ </script>
76
+
77
+ <template>
78
+ <ClientOnly>
79
+ <zitadel-login :project="project" post-sign-in-url="/admin" />
80
+ <!-- on a logout page: -->
81
+ <zitadel-logout :project="project" post-sign-out-url="/login" />
82
+ </ClientOnly>
83
+ </template>
84
+ ```
85
+
86
+ Read auth state in any component via the auto-imported `useAuth()` (it omits the
87
+ raw JWT by design). When you need the token to call upstream APIs, use
88
+ `getAuth(event)` from `@zitadel/sdk-nuxt/server` inside a server route:
89
+
90
+ ```ts
91
+ import { getAuth } from '@zitadel/sdk-nuxt/server';
92
+
93
+ export default defineEventHandler((event) => {
94
+ const auth = getAuth(event);
95
+ if (!auth.isAuthenticated) throw createError({ statusCode: 401 });
96
+ return { userId: auth.session.userId };
97
+ });
98
+ ```
99
+
100
+ The other middleware options (`ignoredRoutes`, `audience`,
101
+ `allowedAlgorithms`, `clockSkewMs`, the timeouts) go under `nextgen` too, and a
102
+ built server takes each one from `NUXT_NEXTGEN_<OPTION>` (a list as JSON). Only
103
+ an app without the module hand-rolls `server/middleware/auth.ts` with
104
+ `createNextgenMiddleware(...)` from `@zitadel/sdk-nuxt/server`.
105
+
106
+ ## Widget props and events
107
+
108
+ The widgets are rendered as raw custom elements, so bind their props by the element's own names, in kebab-case in a template (`post-sign-in-url`, `project-id`). The `@zitadel/sdk-vue` skill lists each prop under its wrapper name in camelCase. The table below lists the events.
109
+
110
+ The widgets are plain custom elements, so each event is a DOM `CustomEvent` that carries its payload in `detail`. The events and payloads are the same ones every SPA SDK exposes as callbacks.
111
+
112
+ | Widget event | Fires when | Detail | Widgets |
113
+ | --- | --- | --- | --- |
114
+ | `zitadel-flow-step` | Login shows a step, the first one included. | `{ step }` | Login |
115
+ | `zitadel-flow-input` | The visitor edits a field. | `{ name, value }` | Login |
116
+ | `zitadel-flow-complete` | The flow reaches a terminal step. | `{ behavior, redirect_uri?, handoff_token?, handoff_token_expires_at? }` | Login |
117
+ | `zitadel-flow-redirect` | The widget hands the browser to an identity provider; nobody is signed in yet. | `{ redirect_url, step }` | Login |
118
+ | `zitadel-flow-error` | The flow reports an error. | `{ message }` | Login |
119
+ | `zitadel-signout` | The user signs out. | `{ display, identifier }` | Logout, Session |
120
+
121
+ ```vue
122
+ <zitadel-login :project="project" post-sign-in-url="/admin" @zitadel-flow-complete="track" />
123
+ ```
124
+
125
+ ## Read these, don't guess
126
+
127
+ The package README at `node_modules/@zitadel/sdk-nuxt/README.md` and the bundled
128
+ `.d.ts` types (`dist/index.d.ts`, `dist/server.d.ts`) are authoritative and
129
+ versioned with the installed package — read them before wiring anything, and let
130
+ them override anything remembered. This skill teaches the pattern, not a full
131
+ catalog of options, attributes, or the shared JWT-verification pipeline (that
132
+ lives in `@zitadel/sdk-core`). Check the exact option names, defaults, and
133
+ component attributes against the installed version rather than assuming.
134
+
135
+ ## Gotchas
136
+
137
+ - **Project id is mandatory.** Without `NUXT_PUBLIC_ZITADEL_PROJECT_ID` (or
138
+ `runtimeConfig.public.zitadelProjectId`) the plugin silently skips
139
+ `configureZitadel()`, so `useZitadelProject()` is `null` and
140
+ `<zitadel-login>` never initializes.
141
+ - **Do not depend on `@zitadel/components`.** The module loads the elements
142
+ itself; importing them in a page `<script setup>` would run during SSR, so
143
+ leave that to the module and wrap the elements in `<ClientOnly>`.
144
+ - **Keep `proxyPath` at `/__nextgen` for external sign-in.** The module moves
145
+ the server proxy and the widgets together, but a provider returns the browser
146
+ to `/__nextgen/idp/{slug}/callback`, which is proxied only under the default
147
+ prefix.
148
+ - **`useAuth()` never carries the JWT.** It is omitted so the token is not
149
+ serialized into the SSR payload; use `getAuth(event)` server-side for it.
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: zitadel-sdk-qwik
3
+ description: >-
4
+ Integrate Zitadel authentication into a Qwik app with `@zitadel/sdk-qwik`.
5
+ Use when the user wants to add login, logout, or a signed-in session card to
6
+ an existing or custom client-side Qwik SPA — wiring the `ZitadelLogin`,
7
+ `ZitadelLogout`, or `ZitadelSession` components and `configureZitadel`.
8
+ ---
9
+
10
+ # @zitadel/sdk-qwik
11
+
12
+ Qwik components that wrap the Zitadel auth UI web components (`<zitadel-login>`,
13
+ `<zitadel-logout>`, `<zitadel-session>`) for client-side SPAs (Vite, etc.). The
14
+ package exports the components, the `configureZitadel` handle factory, and the
15
+ shared SPA config/handler types.
16
+
17
+ ## When to use
18
+
19
+ - **Fresh Qwik app → use the CLI instead.** `npx @zitadel/cli@latest setup --framework qwik`
20
+ scaffolds auth into a new or detected Qwik project end to end (it wires this
21
+ SDK, the dev proxy, and config). Reach for the `zitadel-cli` skill for that.
22
+ Note the CLI scaffolds against **Qwik 2**, which this package also targets.
23
+ - **This skill is for an existing or custom Qwik app** where you are adding the
24
+ components by hand — or adjusting a wiring the CLI already produced.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pnpm add @zitadel/sdk-qwik@latest
30
+ ```
31
+
32
+ The package declares a peer dependency on `@qwik.dev/core` (`^2.0.0-beta.45`),
33
+ so the host app must be on **Qwik 2**. TypeScript ≥ 5.0 is required — the
34
+ published types re-export with `export type *`, a 5.0 feature.
35
+
36
+ ## Integrate
37
+
38
+ Build a project handle with `configureZitadel`, then render a component with it:
39
+
40
+ ```tsx
41
+ import { component$ } from '@qwik.dev/core';
42
+ import { ZitadelLogin, configureZitadel } from '@zitadel/sdk-qwik';
43
+
44
+ export default component$(() => {
45
+ const project = configureZitadel({
46
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
47
+ proxyPath: '/__nextgen',
48
+ });
49
+
50
+ return <ZitadelLogin project={project} purpose="login" postSignInUrl="/" />;
51
+ });
52
+ ```
53
+
54
+ - `<ZitadelLogout project={project} postSignOutUrl="/login" />` works the same way.
55
+ - `<ZitadelSession project={project} />` renders the signed-in session card.
56
+ - Instead of the `project` handle you may pass the discrete `projectId` /
57
+ `proxyPath` props; the widget uses whichever is present.
58
+ - Widget events are optional QRL callbacks; see Widget props and events.
59
+ - The SDK binds `project` (and `projectId` / `proxyPath`) to the element as DOM
60
+ *properties*, matching the other framework SDKs.
61
+
62
+ ## Widget props and events
63
+
64
+ `ZitadelLogin`, `ZitadelLogout` and `ZitadelSession` take the props below, spelled the same in every SPA SDK. An unset prop keeps the widget's own default.
65
+
66
+ | Prop | Type | Default | Widgets | Description |
67
+ | --- | --- | --- | --- | --- |
68
+ | `project` | `ZitadelProject` | unset | Login, Logout, Session | The handle from `configureZitadel(...)`. Wins over `projectId` and `proxyPath`. |
69
+ | `projectId` | `string` | unset | Login, Logout, Session | Project id, read when no `project` handle is passed. |
70
+ | `proxyPath` | `string` | `"/__nextgen"` | Login, Logout, Session | Same-origin path the widget calls. |
71
+ | `purpose` | `CreateFlowBodyPurpose` | `"login"` | Login | Flow purpose. |
72
+ | `flowName` | `string` | unset | Login | Runs the flow definition with this `name`; unset runs the project's default flow for the purpose. |
73
+ | `postSignInUrl` | `string` | unset | Login | Where to navigate after a completed sign-in. When the page's `next` query parameter names a path on the same origin, as the server SDKs' middleware sets it for a signed-out visit to a protected route, the widget returns there instead. |
74
+ | `forcePostSignInUrl` | `boolean` | `false` | Login | Navigates to `postSignInUrl` even when the page's `next` query parameter names a path, for a page that uses `next` for something of its own. |
75
+ | `postSignOutUrl` | `string` | unset | Logout, Session | Where to navigate after sign-out. |
76
+ | `resumeFlowId` | `string` or `null` | unset | Login | An existing flow's id to resume instead of starting a new flow; unset, `null` or `""` falls back to the `flow` query parameter, and an id that no longer resolves starts a new flow. |
77
+ | `variant` | `"widget"` or `"page"` | `"widget"` | Login, Session | `widget` is content-sized and paints no page chrome; `page` claims the viewport, paints the surface background and loads the brand font, and on Login focuses the first field. |
78
+ | `theme` | `"light"`, `"dark"` or `"auto"` | unset | Login, Logout, Session | Colour mode. Unset follows the tenant's branding theme on Login, then the variant default (`dark` for `page`, `auto` for `widget`); Logout defaults to `auto`. `auto` follows `prefers-color-scheme`. |
79
+ | `suppressHeader` | `boolean` | `false` | Login, Session | Visually hides the widget's own heading block while keeping it in the accessibility tree, for pages that already carry the heading. |
80
+ | `suppressPasswordToggle` | `boolean` | `false` | Login | Hides the show/hide button on password fields. |
81
+ | `heading` | `string` | `"Signed in as"` | Session | Heading text override. |
82
+ | `logoutLabel` | `string` | `"Sign out"` | Session | Logout action label override. |
83
+ | `locales` | `Record<string, Partial<Record<string, string>>>` | unset | Login | Copy overrides merged over the builtin dictionaries, keyed by primary language subtag, for example `{ en: { "identifier.title": "Welcome back" } }`. |
84
+ | `lang` | `string` | browser language | Login | BCP-47 language override. |
85
+
86
+ Each widget event reaches the callback in the last column, which receives the event's `detail` payload.
87
+
88
+ | Widget event | Fires when | Detail | Widgets | Callback |
89
+ | --- | --- | --- | --- | --- |
90
+ | `zitadel-flow-step` | Login shows a step, the first one included. | `{ step }` | Login | `onFlowStep$` |
91
+ | `zitadel-flow-input` | The visitor edits a field. | `{ name, value }` | Login | `onFlowInput$` |
92
+ | `zitadel-flow-complete` | The flow reaches a terminal step. | `{ behavior, redirect_uri?, handoff_token?, handoff_token_expires_at? }` | Login | `onFlowComplete$` |
93
+ | `zitadel-flow-redirect` | The widget hands the browser to an identity provider; nobody is signed in yet. | `{ redirect_url, step }` | Login | `onFlowRedirect$` |
94
+ | `zitadel-flow-error` | The flow reports an error. | `{ message }` | Login | `onFlowError$` |
95
+ | `zitadel-signout` | The user signs out. | `{ display, identifier }` | Logout, Session | `onSignout$` |
96
+
97
+ ```tsx
98
+ <ZitadelLogin project={project} theme="light" onFlowComplete$={(detail) => track(detail)} />
99
+ ```
100
+
101
+ When your callback for `zitadel-flow-complete`, `zitadel-flow-redirect` or `zitadel-signout` returns a promise, the widget waits for it to settle (up to two seconds) before it navigates.
102
+
103
+ ## Read these, don't guess
104
+
105
+ Confirm the current surface before wiring — do not rely on memory:
106
+
107
+ - `node_modules/@zitadel/sdk-qwik/README.md` — the installed package's own usage
108
+ notes (proxying, deployment status) are authoritative over this skill.
109
+ - The package's `.d.ts` types (`node_modules/@zitadel/sdk-qwik/dist/index.d.ts`)
110
+ are the source of truth for exports and prop names. The named exports are
111
+ `ZitadelLogin`, `ZitadelLogout`, `ZitadelSession`, `configureZitadel`,
112
+ `getApi`, `getZitadelConfig`, and `businessLocales`; prop types
113
+ (`ZitadelLoginProps`, etc.) and the shared SPA config/handler types are
114
+ re-exported there. Check props (`purpose`, `variant`, `theme`, `flowName`,
115
+ `locales`, `lang`, `ref`, …) against the types rather than assuming them.
116
+
117
+ ## Gotchas
118
+
119
+ - **Server rendering.** If your app renders on the server, pass `projectId` and `proxyPath` to the `ZitadelLogin` wrapper as strings (it writes them as the `project-id` and `proxy-path` attributes; use those hyphenated names only on a raw `<zitadel-login>` element), because a widget that starts before its configuration arrives shows an error instead of the flow. `ZitadelLogout` and `ZitadelSession` do the same, and the other string and boolean options (`purpose`, `flowName`, `postSignInUrl`, `forcePostSignInUrl`, `resumeFlowId`, `postSignOutUrl`, `variant`, `theme`, `heading`, `logoutLabel`, `lang`, `suppressHeader`, `suppressPasswordToggle`) are written as attributes too. A `project` handle contributes its `projectId`, `proxyPath`, `url` and `publishableKey` as attributes; the rest of the handle, and `locales`, are set as element properties after hydration, so a widget that needs `locales` still gets them only on the client.
120
+ - **Qwik 2 only.** The peer dep is `@qwik.dev/core ^2.0.0-beta.45` and imports
121
+ come from `@qwik.dev/core` (not `@builder.io/qwik`). A Qwik 1 app will not
122
+ satisfy the peer range.
123
+ - **Proxying is same-origin.** The widgets call `${proxyPath}/…` same-origin
124
+ (default `/__nextgen`). The CLI dev proxy covers local development. Per the
125
+ README, **production SPA deployment is not yet supported** — the same-origin
126
+ path must come from your hosting platform, and the CLI cannot scaffold those
127
+ configs yet.
128
+ - **Local dev without the proxy.** You can point `proxyPath` straight at the
129
+ backend cross-origin, e.g. `proxyPath: 'http://localhost:8080'` (the
130
+ `zitadel start` local runtime default port).
131
+ - **Properties and attributes.** Qwik 2 binds custom-element JSX props as
132
+ lower-cased attributes only, so the wrapper writes string and boolean config
133
+ as kebab-case attributes (present in server markup) and applies all config,
134
+ including property-only values such as `locales`, as DOM properties on the
135
+ client — which is why the SDK wrapper exists rather than using the web
136
+ components directly.
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: zitadel-sdk-qwik-router
3
+ description: >-
4
+ Integrate Zitadel authentication into an existing or custom Qwik Router app with
5
+ @zitadel/sdk-qwik-router. Use when the user wants login, logout, session
6
+ handling, or protected routes in a Qwik 2 (Qwik Router) app and is wiring the
7
+ SDK by hand rather than letting the CLI scaffold a fresh project — adding the
8
+ request handler, reading auth state in a `routeLoader$`, or rendering the
9
+ `ZitadelLogin` / `ZitadelSession` widgets.
10
+ ---
11
+
12
+ # @zitadel/sdk-qwik-router
13
+
14
+ A Qwik Router request handler and helpers that add Zitadel (Nextgen) auth to a
15
+ Qwik 2 app. The handler proxies auth traffic same-origin under `/__nextgen`,
16
+ verifies the session JWT via JWKS on the server, stores the result on
17
+ `requestEvent.sharedMap`, and redirects unauthenticated requests on protected
18
+ routes. The package is server-only; the widgets come from `@zitadel/sdk-qwik`.
19
+
20
+ ## When to use
21
+
22
+ The `zitadel` CLI (`zitadel setup --framework qwik-router`) scaffolds the handler
23
+ plugin, login/register/profile routes, and `.env.local` into a fresh or detected
24
+ Qwik Router project — prefer it for greenfield work. Reach for **this skill** when
25
+ you are adding auth to an **existing or custom** Qwik Router app by hand, or
26
+ adjusting a scaffolded wiring.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pnpm add -D @zitadel/sdk-qwik-router@latest
32
+ # the login/logout/session widgets are a separate package:
33
+ pnpm add -D @zitadel/sdk-qwik@latest
34
+ ```
35
+
36
+ Install both as **dev dependencies**: Qwik bundles `devDependencies` into the
37
+ server build and leaves `dependencies` external. Peer deps: `@qwik.dev/core` and
38
+ `@qwik.dev/router`, both `^2.0.0-beta.45`. A Qwik 1 app (`@builder.io/qwik-city`)
39
+ must migrate to `@qwik.dev/*` first.
40
+
41
+ ## Integrate
42
+
43
+ **1. Request handler.** Add a root plugin, e.g. `src/routes/plugin@nextgen.ts`:
44
+
45
+ ```ts
46
+ import { createNextgenOnRequest } from '@zitadel/sdk-qwik-router/server';
47
+
48
+ export const onRequest = createNextgenOnRequest({
49
+ protectedRoutes: ['/admin', '/dashboard*'], // trailing * matches sub-paths
50
+ loginPath: '/login',
51
+ });
52
+ ```
53
+
54
+ `url` and `projectSecret` default to `ZITADEL_URL` and `ZITADEL_PROJECT_SECRET`,
55
+ read per request from Qwik Router's `requestEvent.env` (where Vite loads
56
+ `.env.local`), falling back to `process.env`; pass them only to override.
57
+ Sessions count only in the app's own project: `projectId`, else
58
+ `ZITADEL_PROJECT_ID`, else `PUBLIC_ZITADEL_PROJECT_ID`, read the same way. With
59
+ none set, nobody is signed in. To run
60
+ alongside your own handler, export an array: `[createNextgenOnRequest({...}), myOnRequest]`.
61
+
62
+ **2. Read auth server-side** with `getAuth(ev)` in a `routeLoader$`, action, or
63
+ endpoint:
64
+
65
+ ```ts
66
+ import { routeLoader$ } from '@qwik.dev/router';
67
+ import { getAuth } from '@zitadel/sdk-qwik-router/server';
68
+
69
+ export const useUser = routeLoader$((ev) => {
70
+ const auth = getAuth(ev);
71
+ if (!auth.isAuthenticated) throw ev.redirect(302, '/login');
72
+ return { userId: auth.session.userId };
73
+ });
74
+ ```
75
+
76
+ `protectedRoutes` only guards full page loads. To keep a route from rendering at
77
+ all without a session, export a loader like this one from the route itself —
78
+ Qwik Router runs it on the server for `<Link>` navigation too.
79
+
80
+ **3. Render the widgets** from `@zitadel/sdk-qwik`:
81
+
82
+ ```tsx
83
+ import { component$ } from '@qwik.dev/core';
84
+ import { ZitadelLogin, configureZitadel } from '@zitadel/sdk-qwik';
85
+
86
+ const project = configureZitadel({
87
+ projectId: import.meta.env.PUBLIC_ZITADEL_PROJECT_ID,
88
+ proxyPath: '/__nextgen',
89
+ });
90
+
91
+ export default component$(() => (
92
+ <ZitadelLogin project={project} purpose="login" postSignInUrl="/admin" />
93
+ ));
94
+ ```
95
+
96
+ `<ZitadelSession project={project} postSignOutUrl="/login" />` renders the
97
+ signed-in card with sign-out, and `<ZitadelLogout>` a sign-out control on its own.
98
+
99
+ ## Widget props and events
100
+
101
+ This SDK is server-only. The widgets come from `@zitadel/sdk-qwik`, so read its skill for the widget props and events.
102
+
103
+ ## Read these, don't guess
104
+
105
+ The package README at `node_modules/@zitadel/sdk-qwik-router/README.md` and the
106
+ bundled `.d.ts` types (`dist/server.d.ts`, `dist/client.d.ts`) are authoritative
107
+ and versioned with the installed package — read them before wiring anything, and
108
+ let them override anything remembered. This skill teaches the pattern, not a
109
+ full catalog of options or the shared JWT-verification pipeline (that lives in
110
+ `@zitadel/sdk-core`). For widget props and QRL callbacks, read
111
+ `node_modules/@zitadel/sdk-qwik/README.md` and its types. Check exact option
112
+ names and defaults against the installed version rather than assuming.
113
+
114
+ ## Gotchas
115
+
116
+ - **Qwik 2 only.** Imports come from `@qwik.dev/core` and `@qwik.dev/router`
117
+ (not `@builder.io/qwik-city`); a Qwik 1 app will not satisfy the peer range.
118
+ - **Dev dependency, not dependency.** Qwik's starter `vite.config.ts` throws if a
119
+ package is listed in both sections, and a regular dependency stays external to
120
+ the server bundle.
121
+ - **`@zitadel/sdk-qwik-router` renders no widgets.** They come from
122
+ `@zitadel/sdk-qwik`.
123
+ - **Env comes from `requestEvent.env`.** Vite loads `.env.local` there, not onto
124
+ `process.env`, so the handler reads it per request.
125
+ - **Client-side `<Link>` navigation skips the handler.** Without a route
126
+ loader guard, only the `ZitadelSession` card sends a signed-out visitor away.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: zitadel-sdk-react
3
+ description: >-
4
+ Integrate Zitadel authentication into an existing React app with
5
+ `@zitadel/sdk-react`. Use when adding Zitadel login, registration, logout, or
6
+ session handling to a client-side React / Vite SPA or a TanStack
7
+ (Router/Start) app — wiring the `ZitadelLogin`, `ZitadelLogout`, and
8
+ `ZitadelSession` auth-UI components into pages you already have.
9
+ ---
10
+
11
+ # @zitadel/sdk-react
12
+
13
+ React wrappers around the Zitadel auth-UI web components, for client-side SPAs.
14
+
15
+ ## When to use
16
+
17
+ Use this skill to drop the Zitadel auth UI into an **existing or custom** React
18
+ app — your own routes, your own bundler (Vite, TanStack Start, etc.) — by
19
+ rendering the SDK's components on your login/profile pages.
20
+
21
+ If the user instead wants a **fresh** app scaffolded, the CLI does that:
22
+ `npx @zitadel/cli@latest setup --framework react` generates a wired React project. Reach for
23
+ this skill when there is already an app to integrate into and the CLI's
24
+ scaffolding would overwrite or not fit it.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ npm install @zitadel/sdk-react@latest
30
+ ```
31
+
32
+ Peer dependencies: `react >=18` and `react-dom >=18`. Requires TypeScript ≥ 5.0 (the published types use `export type *`).
33
+
34
+ ## Integrate
35
+
36
+ Call `configureZitadel(...)` once before rendering and pass the returned handle
37
+ as the `project` prop. The SPA renders after configuration, so the handle is
38
+ present when the underlying web component upgrades.
39
+
40
+ ```tsx
41
+ import {
42
+ ZitadelLogin,
43
+ ZitadelLogout,
44
+ ZitadelSession,
45
+ configureZitadel,
46
+ } from "@zitadel/sdk-react";
47
+
48
+ // Configure once, module scope, before rendering.
49
+ const project = configureZitadel({
50
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID, // or your env source
51
+ proxyPath: "/__nextgen", // same-origin path your deploy proxies to the backend
52
+ });
53
+
54
+ export function LoginPage() {
55
+ return <ZitadelLogin project={project} purpose="login" postSignInUrl="/" />;
56
+ }
57
+
58
+ export function ProfilePage() {
59
+ return (
60
+ <>
61
+ <ZitadelSession project={project} />
62
+ <ZitadelLogout project={project} postSignOutUrl="/login" />
63
+ </>
64
+ );
65
+ }
66
+ ```
67
+
68
+ - `purpose` defaults to `"login"`; set it for other flows.
69
+ - `postSignInUrl` / `postSignOutUrl` are where the widget sends the user after
70
+ the flow completes.
71
+ - The widget's `zitadel-*` events reach you as the `on*` callbacks listed under
72
+ Widget props and events.
73
+
74
+ ## Widget props and events
75
+
76
+ `ZitadelLogin`, `ZitadelLogout` and `ZitadelSession` take the props below, spelled the same in every SPA SDK. An unset prop keeps the widget's own default.
77
+
78
+ | Prop | Type | Default | Widgets | Description |
79
+ | --- | --- | --- | --- | --- |
80
+ | `project` | `ZitadelProject` | unset | Login, Logout, Session | The handle from `configureZitadel(...)`. Wins over `projectId` and `proxyPath`. |
81
+ | `projectId` | `string` | unset | Login, Logout, Session | Project id, read when no `project` handle is passed. |
82
+ | `proxyPath` | `string` | `"/__nextgen"` | Login, Logout, Session | Same-origin path the widget calls. |
83
+ | `purpose` | `CreateFlowBodyPurpose` | `"login"` | Login | Flow purpose. |
84
+ | `flowName` | `string` | unset | Login | Runs the flow definition with this `name`; unset runs the project's default flow for the purpose. |
85
+ | `postSignInUrl` | `string` | unset | Login | Where to navigate after a completed sign-in. When the page's `next` query parameter names a path on the same origin, as the server SDKs' middleware sets it for a signed-out visit to a protected route, the widget returns there instead. |
86
+ | `forcePostSignInUrl` | `boolean` | `false` | Login | Navigates to `postSignInUrl` even when the page's `next` query parameter names a path, for a page that uses `next` for something of its own. |
87
+ | `postSignOutUrl` | `string` | unset | Logout, Session | Where to navigate after sign-out. |
88
+ | `resumeFlowId` | `string` or `null` | unset | Login | An existing flow's id to resume instead of starting a new flow; unset, `null` or `""` falls back to the `flow` query parameter, and an id that no longer resolves starts a new flow. |
89
+ | `variant` | `"widget"` or `"page"` | `"widget"` | Login, Session | `widget` is content-sized and paints no page chrome; `page` claims the viewport, paints the surface background and loads the brand font, and on Login focuses the first field. |
90
+ | `theme` | `"light"`, `"dark"` or `"auto"` | unset | Login, Logout, Session | Colour mode. Unset follows the tenant's branding theme on Login, then the variant default (`dark` for `page`, `auto` for `widget`); Logout defaults to `auto`. `auto` follows `prefers-color-scheme`. |
91
+ | `suppressHeader` | `boolean` | `false` | Login, Session | Visually hides the widget's own heading block while keeping it in the accessibility tree, for pages that already carry the heading. |
92
+ | `suppressPasswordToggle` | `boolean` | `false` | Login | Hides the show/hide button on password fields. |
93
+ | `heading` | `string` | `"Signed in as"` | Session | Heading text override. |
94
+ | `logoutLabel` | `string` | `"Sign out"` | Session | Logout action label override. |
95
+ | `locales` | `Record<string, Partial<Record<string, string>>>` | unset | Login | Copy overrides merged over the builtin dictionaries, keyed by primary language subtag, for example `{ en: { "identifier.title": "Welcome back" } }`. |
96
+ | `lang` | `string` | browser language | Login | BCP-47 language override. |
97
+
98
+ Each widget event reaches the callback in the last column, which receives the event's `detail` payload.
99
+
100
+ | Widget event | Fires when | Detail | Widgets | Callback |
101
+ | --- | --- | --- | --- | --- |
102
+ | `zitadel-flow-step` | Login shows a step, the first one included. | `{ step }` | Login | `onFlowStep` |
103
+ | `zitadel-flow-input` | The visitor edits a field. | `{ name, value }` | Login | `onFlowInput` |
104
+ | `zitadel-flow-complete` | The flow reaches a terminal step. | `{ behavior, redirect_uri?, handoff_token?, handoff_token_expires_at? }` | Login | `onFlowComplete` |
105
+ | `zitadel-flow-redirect` | The widget hands the browser to an identity provider; nobody is signed in yet. | `{ redirect_url, step }` | Login | `onFlowRedirect` |
106
+ | `zitadel-flow-error` | The flow reports an error. | `{ message }` | Login | `onFlowError` |
107
+ | `zitadel-signout` | The user signs out. | `{ display, identifier }` | Logout, Session | `onSignout` |
108
+
109
+ ```tsx
110
+ <ZitadelLogin project={project} theme="light" onFlowComplete={(detail) => track(detail)} />
111
+ ```
112
+
113
+ When your callback for `zitadel-flow-complete`, `zitadel-flow-redirect` or `zitadel-signout` returns a promise, the widget waits for it to settle (up to two seconds) before it navigates.
114
+
115
+ ## Read these, don't guess
116
+
117
+ This skill is the integration **pattern**, not an API catalog — exact names,
118
+ props, and defaults are authoritative only at the source. Before relying on a
119
+ specific prop or export, read:
120
+
121
+ - `node_modules/@zitadel/sdk-react/README.md` — the installed package's own docs.
122
+ - The package's TypeScript types (`node_modules/@zitadel/sdk-react/dist/index.d.ts`
123
+ and its re-exported `@zitadel/sdk-core/types`) — the authoritative list of
124
+ exported components, props, and event-detail shapes.
125
+
126
+ Discover from those rather than assuming; the package and this skill version
127
+ independently and a hardcoded API list here would drift.
128
+
129
+ ## Gotchas
130
+
131
+ - **Server rendering.** If your app renders on the server, mount the widget on the client only, after hydration, or pass `project-id` and `proxy-path` to the `ZitadelLogin` wrapper as strings (they are written into the server markup as attributes), because a widget that starts before its configuration arrives shows an error instead of the flow. React 19 does not apply an object property such as `project` while hydrating a server-rendered custom element, so on a server-rendered page pass `project-id` and `proxy-path` as strings instead of a `project` handle.
132
+ - **TypeScript ≥ 5.0 required** — the published types use `export type *`, which
133
+ older TypeScript cannot parse.
134
+ - **Browser web components.** These wrap browser web components that only start
135
+ in the browser. A server render emits a bare element, so follow the
136
+ server-rendering gotcha above: string `project-id` and `proxy-path` may be
137
+ server-rendered, while an object such as `project` is client-only because
138
+ React 19 does not apply object properties while hydrating.
139
+ - **Same-origin proxy.** The widgets call `${proxyPath}/…` same-origin (default
140
+ `/__nextgen`). Your deployment must proxy that path to the Zitadel backend.
141
+ Production SPA deployment is not yet fully supported — see
142
+ [ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md)
143
+ and [zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). The
144
+ CLI dev proxy covers local development.
145
+ - **Local dev without a proxy.** You can point `proxyPath` straight at the
146
+ backend cross-origin, e.g. `proxyPath: "http://localhost:8080"` (the
147
+ `zitadel start` default port).
148
+ - **Configure before render.** Build the handle with `configureZitadel(...)` at
149
+ module scope and pass it as `project`, so it is set when the element upgrades.