@zitadel/sdk-nuxt 0.1.0-alpha.17 → 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.
- package/README.md +99 -73
- package/dist/{chunk-AQZKZRKQ.js → chunk-MJ3RLEU6.js} +13 -6
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -1
- package/dist/module.js +1 -1
- package/dist/runtime/server/handler.js +1 -1
- package/dist/runtime/server/middleware.js +1 -1
- package/dist/runtime/types.d.ts +1 -37
- package/dist/server.js +1 -1
- 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
|
-
|
|
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
|
-
|
|
15
|
+
## Surface 1 — the Nuxt module (recommended, CLI-scaffolded)
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
41
|
-
|
|
31
|
+
public: {
|
|
32
|
+
zitadelProjectId: process.env.NUXT_PUBLIC_ZITADEL_PROJECT_ID ?? '',
|
|
42
33
|
},
|
|
43
34
|
},
|
|
44
35
|
});
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
(This mirrors what `zitadel setup` writes into `nuxt.config.ts`.)
|
|
48
39
|
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
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
|
|
121
|
+
<zitadel-login :project="project" post-sign-in-url="/admin" />
|
|
103
122
|
</ClientOnly>
|
|
104
123
|
</main>
|
|
105
124
|
</template>
|
|
106
125
|
```
|
|
107
126
|
|
|
108
|
-
|
|
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 {
|
|
133
|
+
import { createNextgenMiddleware } from '@zitadel/sdk-nuxt/server';
|
|
112
134
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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.
|
|
@@ -96,7 +96,7 @@ async function proxyRequest(event, authUrl, proxyPath, url, proxyTimeoutMs) {
|
|
|
96
96
|
const rawBody = hasBody ? await readRawBody(event, false) : void 0;
|
|
97
97
|
const body = rawBody != null ? new Uint8Array(rawBody) : void 0;
|
|
98
98
|
const upstreamHeaders = buildUpstreamHeaders(event);
|
|
99
|
-
if (!upstreamHeaders.has("authorization")) {
|
|
99
|
+
if (requiresProjectSecret(method, suffix) && !upstreamHeaders.has("authorization")) {
|
|
100
100
|
const config = useRuntimeConfig();
|
|
101
101
|
const projectSecret = config.nextgen?.projectSecret;
|
|
102
102
|
if (projectSecret) {
|
|
@@ -130,6 +130,9 @@ async function proxyRequest(event, authUrl, proxyPath, url, proxyTimeoutMs) {
|
|
|
130
130
|
}
|
|
131
131
|
return response.body;
|
|
132
132
|
}
|
|
133
|
+
function requiresProjectSecret(method, pathname) {
|
|
134
|
+
return method.toUpperCase() === "POST" && pathname === "/sessions/exchange";
|
|
135
|
+
}
|
|
133
136
|
var DECODER = new TextDecoder();
|
|
134
137
|
function isJwtShaped(token) {
|
|
135
138
|
const parts = token.split(".");
|
|
@@ -193,13 +196,14 @@ async function handleAuth(event, opts) {
|
|
|
193
196
|
};
|
|
194
197
|
return;
|
|
195
198
|
}
|
|
199
|
+
let liveAnonymousSession = false;
|
|
196
200
|
if (!payload && cookieToken && !isJwtShaped(cookieToken)) {
|
|
197
201
|
const opaqueResult = await validateOpaqueSessionToken(cookieToken, url, opaqueTokenTimeoutMs);
|
|
198
|
-
if (opaqueResult) {
|
|
202
|
+
if (opaqueResult?.userId) {
|
|
199
203
|
event.context.nextgenAuth = {
|
|
200
204
|
isAuthenticated: true,
|
|
201
205
|
session: {
|
|
202
|
-
userId: opaqueResult.userId
|
|
206
|
+
userId: opaqueResult.userId,
|
|
203
207
|
email: null,
|
|
204
208
|
name: null,
|
|
205
209
|
token: cookieToken
|
|
@@ -207,11 +211,14 @@ async function handleAuth(event, opts) {
|
|
|
207
211
|
};
|
|
208
212
|
return;
|
|
209
213
|
}
|
|
214
|
+
liveAnonymousSession = opaqueResult !== null;
|
|
210
215
|
}
|
|
211
216
|
event.context.nextgenAuth = { isAuthenticated: false, session: null };
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
if (!liveAnonymousSession) {
|
|
218
|
+
for (const name of Object.keys(parseCookies(event))) {
|
|
219
|
+
if (name.startsWith("__nextgen")) {
|
|
220
|
+
deleteCookie(event, name);
|
|
221
|
+
}
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
if (matchesRoutes(pathname, protectedRoutes)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export { ZitadelLogin, ZitadelLogout } from '@zitadel/components';
|
|
1
|
+
export { ZitadelLogin, ZitadelLogout, businessLocales } from '@zitadel/components';
|
|
2
2
|
export { createNextgenMiddleware, getAuth } from './runtime/server/middleware.js';
|
|
3
3
|
import { ZitadelProject } from '@zitadel/api/config';
|
|
4
|
-
export { ClientAuthResult } from '
|
|
5
|
-
export { AuthResult, NextgenMiddlewareOptions } from '@zitadel/sdk-core/middleware';
|
|
4
|
+
export { AuthResult, ClientAuthResult, NextgenMiddlewareOptions } from '@zitadel/sdk-core/middleware';
|
|
6
5
|
import 'h3';
|
|
7
6
|
|
|
8
7
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,11 @@ import "./chunk-ZNXAFGLP.js";
|
|
|
2
2
|
import {
|
|
3
3
|
createNextgenMiddleware,
|
|
4
4
|
getAuth
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MJ3RLEU6.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import { ZitadelLogin, ZitadelLogout } from "@zitadel/components";
|
|
9
|
+
import { businessLocales } from "@zitadel/components";
|
|
9
10
|
|
|
10
11
|
// src/runtime/composables/useZitadelProject.ts
|
|
11
12
|
import { getZitadelConfig } from "@zitadel/api/config";
|
|
@@ -15,6 +16,7 @@ function useZitadelProject() {
|
|
|
15
16
|
export {
|
|
16
17
|
ZitadelLogin,
|
|
17
18
|
ZitadelLogout,
|
|
19
|
+
businessLocales,
|
|
18
20
|
createNextgenMiddleware,
|
|
19
21
|
getAuth,
|
|
20
22
|
useZitadelProject
|
package/dist/module.js
CHANGED
|
@@ -48,7 +48,7 @@ var module_default = defineNuxtModule({
|
|
|
48
48
|
// Server-only — never exposed to the client. Override at deploy time via
|
|
49
49
|
// `NUXT_NEXTGEN_PROJECT_SECRET`. Left undefined when the env var (and
|
|
50
50
|
// `.env.local`) provide no value, so the middleware surfaces a missing
|
|
51
|
-
// bearer instead of silently sending an empty one.
|
|
51
|
+
// exchange bearer instead of silently sending an empty one.
|
|
52
52
|
projectSecret: loadProjectSecret(nuxt.options.rootDir)
|
|
53
53
|
};
|
|
54
54
|
nuxt.options.runtimeConfig.public.zitadelProxyPath = options.proxyPath ?? "/__nextgen";
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,37 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { AuthResult, AuthState, NextgenMiddlewareOptions, NextgenSession, UnauthState } from '@zitadel/sdk-core/middleware';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Re-exports shared SDK types from `@zitadel/sdk-core`.
|
|
6
|
-
*
|
|
7
|
-
* These types are defined once in sdk-core and shared by both sdk-next
|
|
8
|
-
* and sdk-nuxt.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The client-safe session exposed to Vue components via {@link useAuth}.
|
|
13
|
-
* Identical to {@link NextgenSession} but omits `token` — the raw JWT must
|
|
14
|
-
* not be serialised into the Nuxt SSR payload where third-party scripts can
|
|
15
|
-
* read it.
|
|
16
|
-
*/
|
|
17
|
-
type ClientSession = {
|
|
18
|
-
/** The user's unique identifier (`sub` claim). */
|
|
19
|
-
userId: string;
|
|
20
|
-
/** The user's email address, or `null` if not present in the token. */
|
|
21
|
-
email: string | null;
|
|
22
|
-
/** The user's display name, or `null` if not present in the token. */
|
|
23
|
-
name: string | null;
|
|
24
|
-
};
|
|
25
|
-
/** Client-safe auth state when the user is signed in. */
|
|
26
|
-
type ClientAuthState = {
|
|
27
|
-
isAuthenticated: true;
|
|
28
|
-
session: ClientSession;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Union of all possible auth states returned by {@link useAuth}.
|
|
32
|
-
* Token is intentionally absent — use {@link getAuth} server-side when the
|
|
33
|
-
* raw JWT is needed.
|
|
34
|
-
*/
|
|
35
|
-
type ClientAuthResult = ClientAuthState | UnauthState;
|
|
36
|
-
|
|
37
|
-
export type { ClientAuthResult, ClientAuthState, ClientSession };
|
|
1
|
+
export { AuthResult, AuthState, ClientAuthResult, ClientAuthState, ClientSession, NextgenMiddlewareOptions, NextgenSession, UnauthState } from '@zitadel/sdk-core/middleware';
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zitadel/sdk-nuxt",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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/api": "0.1.0-alpha.
|
|
37
|
-
"@zitadel/sdk-core": "0.1.0-alpha.
|
|
38
|
-
"@zitadel/components": "0.1.0-alpha.
|
|
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.
|
|
56
|
+
"nuxt": "^4.5.1",
|
|
57
57
|
"prettier": "^3.0.0",
|
|
58
58
|
"tsup": "^8.3.5",
|
|
59
59
|
"typescript": "^5.7.3",
|