@zitadel/sdk-nuxt 0.1.0-alpha.18 → 0.1.0-alpha.19

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 (2) hide show
  1. package/README.md +99 -73
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zitadel/sdk-nuxt
2
2
 
3
- Nuxt middleware and helpers for Nextgen Auth.
3
+ Nuxt module, Nitro middleware, and helpers for Nextgen Auth.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,67 +8,66 @@ Nuxt middleware and helpers for Nextgen Auth.
8
8
  pnpm add @zitadel/sdk-nuxt
9
9
  ```
10
10
 
11
- ## Setup
11
+ There are two ways to wire the SDK. **The module is what `zitadel setup`
12
+ scaffolds** and the recommended path; the direct-middleware surface remains
13
+ for hand-rolled setups.
12
14
 
13
- ### 1. Server middleware
15
+ ## Surface 1 the Nuxt module (recommended, CLI-scaffolded)
14
16
 
15
- Create `server/middleware/auth.ts`:
16
-
17
- ```ts
18
- import { createNextgenMiddleware } from '@zitadel/sdk-nuxt/server';
19
-
20
- const { nextgen } = useRuntimeConfig();
21
-
22
- export default createNextgenMiddleware({
23
- url: nextgen.url,
24
- protectedRoutes: ['/admin', '/dashboard*'],
25
- loginPath: '/login',
26
- });
27
- ```
28
-
29
- The middleware runs on every request and does three things in one pass:
30
-
31
- 1. **Proxies** `/__nextgen/*` requests to the auth backend
32
- 2. **Verifies** the session JWT via JWKS using the Web Crypto API
33
- 3. **Redirects** unauthenticated requests to `loginPath` for protected routes
34
-
35
- Add the issuer URL to `nuxt.config.ts`:
17
+ Register the module under the `nextgen` config key, and give the client
18
+ plugin your project id via `runtimeConfig.public.zitadelProjectId` — without
19
+ it the plugin skips `configureZitadel()`, `useZitadelProject()` returns
20
+ `null`, and the login widget cannot initialize:
36
21
 
37
22
  ```ts
38
23
  export default defineNuxtConfig({
24
+ modules: ['@zitadel/sdk-nuxt/module'],
25
+ nextgen: {
26
+ url: process.env.ZITADEL_URL ?? 'http://localhost:8080',
27
+ protectedRoutes: ['/admin', '/dashboard*'],
28
+ loginPath: '/login',
29
+ },
39
30
  runtimeConfig: {
40
- nextgen: {
41
- url: process.env.ZITADEL_URL ?? 'http://localhost:4000',
31
+ public: {
32
+ zitadelProjectId: process.env.NUXT_PUBLIC_ZITADEL_PROJECT_ID ?? '',
42
33
  },
43
34
  },
44
35
  });
45
36
  ```
46
37
 
47
- ### 2. Plugin
38
+ (This mirrors what `zitadel setup` writes into `nuxt.config.ts`.)
48
39
 
49
- Create `plugins/auth.server.ts` to make auth state available in pages:
40
+ The module registers the Nitro server middleware, the auth plugin (which
41
+ seeds server-side auth state, hydrates it on the client, and calls
42
+ `configureZitadel()` from the runtime config above), and auto-imports the
43
+ composables — no `server/middleware/auth.ts`, no manual plugin.
50
44
 
51
- ```ts
52
- import { defineNuxtPlugin, useRequestEvent, useState } from '#imports';
53
-
54
- export default defineNuxtPlugin(() => {
55
- const event = useRequestEvent();
56
- const auth = event?.context.nextgenAuth ?? {
57
- isAuthenticated: false as const,
58
- session: null,
59
- };
60
- useState('nextgen-auth', () => auth);
61
- });
62
- ```
45
+ ### Module options (what the module actually forwards)
46
+
47
+ | Option | Type | Default | Description |
48
+ | --- | --- | --- | --- |
49
+ | `url` | `string` | `ZITADEL_URL` env, else `http://localhost:8080` | Full URL of the Zitadel auth backend |
50
+ | `proxyPath` | `string` | `"/__nextgen"` | The path the **client** widgets call (exposed via public runtime config). The registered server handler currently always serves `/__nextgen` regardless of this option — to actually move the server prefix, use the direct-middleware surface |
51
+ | `protectedRoutes` | `string[]` | `[]` | Paths requiring a valid session. Trailing `*` matches sub-paths |
52
+ | `loginPath` | `string` | `"/login"` | Where to redirect unauthenticated users |
63
53
 
64
- ### 3. Reading auth in a page
54
+ The module's registered handler reads only `url`, `protectedRoutes`, and
55
+ `loginPath` from runtime config; `proxyPath` configures the client side only
56
+ (see its row above). The fine-tuning options of the direct-middleware surface
57
+ (`ignoredRoutes`, `allowedAlgorithms`, `allowedTokenTypes`, `clockSkewMs`,
58
+ `jwksTimeoutMs`, `opaqueTokenTimeoutMs`, `proxyTimeoutMs`, `audience`) are
59
+ **not currently forwarded by the module** — if you need them, use Surface 2.
60
+ The module also loads the server-only project secret into runtime config at
61
+ startup, from the `ZITADEL_PROJECT_SECRET` environment variable or the
62
+ scaffolded `.env.local`.
63
+
64
+ ### Reading auth state
65
+
66
+ `useAuth()` is auto-imported:
65
67
 
66
68
  ```vue
67
69
  <script setup lang="ts">
68
- const auth = useState('nextgen-auth');
69
- if (auth.value?.isAuthenticated) {
70
- await navigateTo('/admin');
71
- }
70
+ const auth = useAuth();
72
71
  </script>
73
72
 
74
73
  <template>
@@ -76,10 +75,30 @@ if (auth.value?.isAuthenticated) {
76
75
  </template>
77
76
  ```
78
77
 
79
- ### 4. Register components (client only)
78
+ The returned state intentionally omits the raw JWT — use `getAuth(event)` in
79
+ a server route when you need the token to call upstream APIs:
80
+
81
+ ```ts
82
+ import { getAuth } from '@zitadel/sdk-nuxt/server';
83
+
84
+ export default defineEventHandler((event) => {
85
+ const auth = getAuth(event);
86
+ if (!auth.isAuthenticated) throw createError({ statusCode: 401 });
87
+ return { userId: auth.session.userId };
88
+ });
89
+ ```
90
+
91
+ ### Login page
92
+
93
+ Register the shared components in a client-only plugin
94
+ (`plugins/zitadel-components.client.ts` — do **not** import
95
+ `@zitadel/components` from page `<script setup>`, that runs during SSR).
96
+ Under strict package managers (pnpm, Yarn PnP) `@zitadel/components` is not
97
+ resolvable as a transitive dependency, so declare it in your app first:
80
98
 
81
- Create `plugins/zitadel-components.client.ts` — do **not** import
82
- `@zitadel/components` from page `<script setup>` (that runs during SSR):
99
+ ```bash
100
+ pnpm add @zitadel/components
101
+ ```
83
102
 
84
103
  ```ts
85
104
  import "@zitadel/components";
@@ -87,37 +106,48 @@ import "@zitadel/components";
87
106
  export default defineNuxtPlugin(() => {});
88
107
  ```
89
108
 
90
- Set `body { margin: 0; font-family: sans-serif; }` in `app.vue` (see
91
- [`apps/demo-nuxt`](../../apps/demo-nuxt/README.md)). Arimo loads from
92
- `branding.font_url` inside `<zitadel-login>` when the mock/API supplies it.
93
-
94
- ### 5. Login page
95
-
96
- Render `<zitadel-login>` inside `<ClientOnly>`:
109
+ Then render `<zitadel-login>` inside `<ClientOnly>`, binding the project
110
+ handle from the auto-imported `useZitadelProject()` composable there is no
111
+ `api-base` attribute:
97
112
 
98
113
  ```vue
114
+ <script setup lang="ts">
115
+ const project = useZitadelProject();
116
+ </script>
117
+
99
118
  <template>
100
119
  <main>
101
120
  <ClientOnly>
102
- <zitadel-login api-base="/__nextgen" project-id="demo" post-sign-in-url="/admin" />
121
+ <zitadel-login :project="project" post-sign-in-url="/admin" />
103
122
  </ClientOnly>
104
123
  </main>
105
124
  </template>
106
125
  ```
107
126
 
108
- ### 6. Reading auth in a server route
127
+ ## Surface 2 direct middleware (hand-rolled)
128
+
129
+ Create `server/middleware/auth.ts` yourself when you need the full option
130
+ set:
109
131
 
110
132
  ```ts
111
- import { getAuth } from '@zitadel/sdk-nuxt/server';
133
+ import { createNextgenMiddleware } from '@zitadel/sdk-nuxt/server';
112
134
 
113
- export default defineEventHandler((event) => {
114
- const auth = getAuth(event);
115
- if (!auth.isAuthenticated) throw createError({ statusCode: 401 });
116
- return { userId: auth.session.userId };
135
+ const { nextgen } = useRuntimeConfig();
136
+
137
+ export default createNextgenMiddleware({
138
+ url: nextgen.url,
139
+ protectedRoutes: ['/admin', '/dashboard*'],
140
+ loginPath: '/login',
117
141
  });
118
142
  ```
119
143
 
120
- ## Middleware options
144
+ The middleware runs on every request and does three things in one pass:
145
+
146
+ 1. **Proxies** `/__nextgen/*` requests to the auth backend
147
+ 2. **Verifies** the session JWT via JWKS using the Web Crypto API
148
+ 3. **Redirects** unauthenticated requests to `loginPath` for protected routes
149
+
150
+ ### Direct-middleware options
121
151
 
122
152
  | Option | Type | Default | Description |
123
153
  | ------------------- | -------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
@@ -130,17 +160,13 @@ export default defineEventHandler((event) => {
130
160
  | `allowedTokenTypes` | `string[]` | `["JWT", "at+JWT"]` | Accepted `typ` header values (case-insensitive). Set to `[]` to disable this check |
131
161
  | `clockSkewMs` | `number` | `5000` | Clock skew tolerance in ms for `exp`, `nbf`, `iat` |
132
162
  | `jwksTimeoutMs` | `number` | `5000` | Timeout in ms for JWKS endpoint requests. Token is rejected if the fetch exceeds this window |
163
+ | `opaqueTokenTimeoutMs` | `number` | `5000` | Timeout in ms for opaque (non-JWT) session validation via `GET /sessions/me` |
164
+ | `proxyTimeoutMs` | `number` | `5000` | Timeout in ms for upstream proxy requests; requests exceeding it abort with a network error |
133
165
  | `audience` | `string \| string[]` | not validated | Expected `aud` claim value(s). When omitted, audience is not checked |
134
166
 
135
167
  ## How JWT verification works
136
168
 
137
- 1. Bearer token from `Authorization` header is checked first; `__nextgen_session` cookie is the fallback
138
- 2. The JWT header is decoded to extract `kid` and `alg`
139
- 3. Tokens with an `alg` not in `allowedAlgorithms` (`RS256`, `ES256` by default) are rejected immediately — no JWKS fetch
140
- 4. Tokens with a `typ` not in `allowedTokenTypes` are rejected immediately
141
- 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`
142
- 6. The signature is verified **before** any claim checks
143
- 7. `iss` must be present and must equal `url` — tokens without an issuer are rejected
144
- 8. `exp` must be present and must be in the future (with `clockSkewMs` tolerance) — tokens without an expiry are rejected
145
- 9. `nbf` and `iat` are validated with `clockSkewMs` tolerance when present
146
- 10. The `x-nextgen-auth-token` header is stripped from all proxied requests to prevent internal state leakage
169
+ The verification pipeline is shared across SDKs and documented once in
170
+ [`@zitadel/sdk-core`](https://github.com/zitadel/nextgen/tree/main/packages/sdk-core#how-jwt-verification-works).
171
+ On top of it, the Nitro middleware strips the `x-nextgen-auth-token` header
172
+ from all proxied requests to prevent internal state leakage.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zitadel/sdk-nuxt",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "Nuxt proxy middleware and helpers for the pre-release Zitadel preview.",
5
5
  "homepage": "https://github.com/zitadel/nextgen/tree/main/packages/sdk-nuxt#readme",
6
6
  "bugs": {
@@ -33,9 +33,9 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@zitadel/components": "0.1.0-alpha.18",
37
- "@zitadel/sdk-core": "0.1.0-alpha.18",
38
- "@zitadel/api": "0.1.0-alpha.18"
36
+ "@zitadel/api": "0.1.0-alpha.19",
37
+ "@zitadel/sdk-core": "0.1.0-alpha.19",
38
+ "@zitadel/components": "0.1.0-alpha.19"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "h3": ">=1",
@@ -53,7 +53,7 @@
53
53
  "eslint-plugin-perfectionist": "^4.0.0",
54
54
  "eslint-plugin-prettier": "^5.0.0",
55
55
  "h3": "^1.15.3",
56
- "nuxt": "^4.4.8",
56
+ "nuxt": "^4.5.1",
57
57
  "prettier": "^3.0.0",
58
58
  "tsup": "^8.3.5",
59
59
  "typescript": "^5.7.3",