@authup/client-web-nuxt 1.0.0-beta.69 → 1.0.0-beta.71

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 ADDED
@@ -0,0 +1,166 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/authup/authup/master/.github/assets/logo.svg" alt="Authup logo" width="96" height="96" />
3
+ </p>
4
+
5
+ <h1 align="center">@authup/client-web-nuxt</h1>
6
+
7
+ <p align="center">
8
+ <b>Nuxt module that signs your app in with Authup and guards its routes by login state and permission.</b><br>
9
+ It installs @authup/client-web-kit with Nuxt's cookies, SSR payload and runtime config wired in.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@authup/client-web-nuxt"><img src="https://img.shields.io/npm/v/@authup/client-web-nuxt" alt="npm version" /></a>
14
+ <a href="https://github.com/authup/authup/actions/workflows/main.yml"><img src="https://github.com/authup/authup/actions/workflows/main.yml/badge.svg" alt="CI" /></a>
15
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License: Apache-2.0" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="#installation"><b>Installation</b></a> · <a href="#usage">Usage</a> · <a href="#options">Options</a> · <a href="https://authup.org">Documentation</a>
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## Why @authup/client-web-nuxt?
25
+
26
+ [`@authup/client-web-kit`](https://github.com/authup/authup/tree/master/packages/client-web-kit) is a plain Vue plugin. In a Nuxt app it has to read the session from cookies during the server render, hand what it loaded on the server to the browser, know a different API address on each side, and check every navigation. This module does that wiring once.
27
+
28
+ | Concern | Without it | With it |
29
+ |---|---|---|
30
+ | **Session cookies** | Bridge the kit's cookie functions to `useCookie` on both sides | Wired, including `Secure` from the request's scheme and an optional name prefix |
31
+ | **API address** | Pass one URL that has to work from the browser and from the server | `apiURL` for the browser, `serverApiURL` for the server, kept out of the page |
32
+ | **Route guards** | Write a middleware that resolves the session and checks every route | A global middleware reading `requireLoggedIn`, `requireLoggedOut` and `requirePermissions` page meta |
33
+ | **Login callback** | Redeem `?code=` yourself and strip it from the URL | The middleware exchanges it against the saved PKCE request and lands on the `redirect` path |
34
+ | **Server rendering** | Lists fetched on the server and again on hydration | The kit's hydration handoff rides `nuxtApp.payload.data` |
35
+
36
+ ## Installation
37
+
38
+ ```shell
39
+ npm install @authup/client-web-nuxt @pinia/nuxt pinia
40
+ ```
41
+
42
+ `@pinia/nuxt` and `pinia` are peer dependencies (the module registers `@pinia/nuxt` itself). Install the kit's own peers too, the Vue, `vue-router`, `ilingo`, `validup` and `@vuecs/*` packages listed in [its README](https://github.com/authup/authup/tree/master/packages/client-web-kit#installation), plus a theme ([`@authup/client-web-kit-theme`](https://github.com/authup/authup/tree/master/packages/client-web-kit-theme) or [`@authup/client-web-theme`](https://github.com/authup/authup/tree/master/packages/client-web-theme)). The module is built for Nuxt 4, is ESM-only and needs Node.js `^22.13.0 || ^23.5.0 || >=24.0.0`.
43
+
44
+ ## Usage
45
+
46
+ ### Register the module
47
+
48
+ ```typescript
49
+ export default defineNuxtConfig({
50
+ modules: ['@authup/client-web-nuxt'],
51
+ authup: {
52
+ apiURL: 'https://auth.example.com',
53
+ serverApiURL: 'http://authup:3000',
54
+ },
55
+ css: ['@authup/client-web-theme/index.css'],
56
+ });
57
+ ```
58
+
59
+ Every option except `serverApiURL` lands in the public `runtimeConfig.public.authup`, so `NUXT_PUBLIC_AUTHUP_API_URL` overrides `apiURL` at runtime. `serverApiURL` goes to the private `runtimeConfig.authup` and is overridden with `NUXT_AUTHUP_SERVER_API_URL`. It only changes where the server render sends its requests: every URL the kit's client derives from the API URL (identity provider links, the OAuth2 endpoints) keeps the public one, since hydration does not correct a rendered `href`. A `buildAuthorizeURL` call takes its base from you, so pass it the public `apiURL`.
60
+
61
+ ### Install vuecs after the kit
62
+
63
+ The module installs the kit, not vuecs. Add a plugin that runs after it:
64
+
65
+ ```typescript
66
+ import vuecs from '@vuecs/core';
67
+ import installForms from '@vuecs/forms';
68
+ import clientWebKitTheme from '@authup/client-web-kit-theme';
69
+ import clientWebTheme from '@authup/client-web-theme';
70
+ import { buildVuecsInstallOptions } from '@authup/client-web-kit';
71
+
72
+ export default defineNuxtPlugin({
73
+ name: 'vuecs',
74
+ dependsOn: ['authup:kit'],
75
+ setup(nuxtApp) {
76
+ const options = nuxtApp.vueApp.runWithContext(() => buildVuecsInstallOptions({
77
+ themes: [clientWebKitTheme(), clientWebTheme()],
78
+ }));
79
+
80
+ nuxtApp.vueApp.use(vuecs, options);
81
+ nuxtApp.vueApp.use(installForms);
82
+ },
83
+ });
84
+ ```
85
+
86
+ Use `dependsOn: ['authup:kit']`, not `enforce: 'pre'`: the kit provides the translator `buildVuecsInstallOptions()` reads, and running before it fails the whole plugin chain.
87
+
88
+ ### Guard a page
89
+
90
+ ```vue
91
+ <script lang="ts">
92
+ import { PermissionName } from '@authup/core-kit';
93
+ import { defineComponent } from 'vue';
94
+ import { RouteMetaKey } from '#authup/nuxt';
95
+
96
+ definePageMeta({
97
+ [RouteMetaKey.REQUIRE_LOGGED_IN]: true,
98
+ [RouteMetaKey.REQUIRE_PERMISSIONS]: [PermissionName.USER_READ],
99
+ });
100
+
101
+ export default defineComponent({
102
+ setup() {
103
+ return {};
104
+ },
105
+ });
106
+ </script>
107
+ ```
108
+
109
+ The global `authup` middleware runs on every navigation:
110
+
111
+ 1. A `?code=` in the URL is exchanged on the client against the saved authorization request, then the user lands on the `redirect` query (site-relative paths only) or on the same route without the OAuth2 parameters.
112
+ 2. Otherwise it awaits `store.resolve()`. On failure it signs out and sends the user to `loginRoute` with `redirect` set to the page they asked for.
113
+ 3. `requireLoggedIn` (on the route or any parent) sends a signed-out user to `loginRoute`.
114
+ 4. `requireLoggedOut` signs out a signed-in user and lets the navigation continue.
115
+ 5. `requirePermissions` (a name or a list; any one of them passes) sends a denied user back to the page they came from, home after a fresh login, or to `loginRoute` when they were denied the page they are on.
116
+
117
+ The `authup:session-expiry` plugin also sends the user to `loginRoute` when a background refresh fails on a page that requires login.
118
+
119
+ ### Read the session
120
+
121
+ ```typescript
122
+ import { StoreAuthStatus, injectStore } from '@authup/client-web-kit';
123
+
124
+ const store = injectStore();
125
+ const signedIn = store.status === StoreAuthStatus.AUTHENTICATED;
126
+ ```
127
+
128
+ Everything else (permission checks, the login form, the authorization code helpers) comes from `@authup/client-web-kit`; see its README.
129
+
130
+ ## Options
131
+
132
+ Set under the `authup` key of `nuxt.config`:
133
+
134
+ | Option | Type | Default | Description |
135
+ |---|---|---|---|
136
+ | `apiURL` | `string` | `http://localhost:3001` | API URL for the browser, and for the server when nothing more specific is set. |
137
+ | `serverApiURL` | `string` | `apiURL` | Address the server render dials. URLs the kit renders keep the public API URL. Private runtime config only. |
138
+ | `apiURLRuntimeKey` | `string` | none | Path into the runtime config to read the API URL from: the whole runtime config on the server, the public one in the browser. `serverApiURL` still wins on the server. |
139
+ | `cookieDomain` | `string` | host only | `Domain` attribute of the session cookies. `127.0.0.1` is written as `localhost`. |
140
+ | `cookieDomainRuntimeKey` | `string` | none | Path into the public runtime config to read the cookie domain from. |
141
+ | `cookiePrefix` | `string` | none | Prepended verbatim to every session cookie name (`flame_` writes `flame_access_token`). Use letters, digits, `_`, `-` and `.` only. |
142
+ | `homeRoute` | `string` | `/` | Where a user lands after a fresh login whose target they may not open. |
143
+ | `loginRoute` | `string` | `/login` | Where the middleware sends signed-out and expired users. |
144
+
145
+ ## API
146
+
147
+ | Export | Kind | Purpose |
148
+ |---|---|---|
149
+ | default | Nuxt module | Registers `@pinia/nuxt`, the plugins `authup:kit`, `authup:session-expiry` and `authup`, and the global `authup` route middleware |
150
+ | `ModuleOptions` | type | The `authup` options |
151
+ | `RouteMetaKey` (from `#authup/nuxt`) | enum | `requireLoggedIn`, `requireLoggedOut`, `requirePermissions` |
152
+ | `authup:middleware:start`, `authup:middleware:redirect`, `authup:middleware:end` | runtime hooks | Called by the middleware with `{ to, from }` |
153
+
154
+ ## Notes
155
+
156
+ - **The module brings no login page.** Build `loginRoute` yourself, for example with the kit's `ALoginForm`, or start the authorization code flow from it.
157
+ - **Keep the callback route client-only.** The PKCE verifier lives in `sessionStorage`, so the middleware skips the code exchange during the server render; a `routeRules` entry with `ssr: false` for the callback page avoids rendering it on the server at all.
158
+ - **`cookiePrefix` is a contract across your services.** A resource server of yours that reads the access token from the cookie must read the prefixed name.
159
+ - **The account preferences sync only in the browser.** The plugin hands the kit `useCookie` refs for `vc-locale` and `vc-color-mode`, written with the cookie options of `@vuecs/nuxt` when present, so the account's language and color mode reach your switchers and back.
160
+ - **This module does not enable the kit's `cookieSession` mode**; a Nuxt app keeps the token pair in its own cookies.
161
+
162
+ ## License
163
+
164
+ Made with 💚
165
+
166
+ Published under the [Apache 2.0 License](./LICENSE).
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@authup/client-web-nuxt",
3
3
  "configKey": "authup",
4
- "version": "1.0.0-beta.69",
4
+ "version": "1.0.0-beta.71",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
@@ -0,0 +1,15 @@
1
+ import type { HTTPClientInput } from '@authup/client-web-kit';
2
+ import type { RuntimeConfig } from 'nuxt/schema';
3
+ export type HTTPClientOptions = {
4
+ baseURL: string;
5
+ httpClient?: HTTPClientInput;
6
+ };
7
+ /**
8
+ * The kit's base url is the PUBLIC api url on both sides, because everything
9
+ * derived from it is user-facing: the identity provider buttons, the absolute
10
+ * OAuth2 endpoints and hapic's error messages. A server render only DIALS
11
+ * the server url, through a rebased transport, so a rendered href never
12
+ * names an address the browser cannot reach (hydration does not correct
13
+ * attributes). A function, so the kit gets a new client per consumer.
14
+ */
15
+ export declare function buildHTTPClientOptions(runtimeConfig: RuntimeConfig, isServer: boolean): HTTPClientOptions;
@@ -0,0 +1,46 @@
1
+ import { API_URL_DEFAULT, Client, createRebasedTransport } from "@authup/core-http-kit";
2
+ import { getPathValue } from "pathtrace";
3
+ function readPublicApiURL(runtimeConfig) {
4
+ const options = runtimeConfig.public.authup;
5
+ let url;
6
+ if (options.apiURL) {
7
+ url = options.apiURL;
8
+ }
9
+ if (options.apiURLRuntimeKey) {
10
+ const pathValue = getPathValue(runtimeConfig.public, options.apiURLRuntimeKey);
11
+ if (typeof pathValue === "string") {
12
+ url = pathValue;
13
+ }
14
+ }
15
+ return url || API_URL_DEFAULT;
16
+ }
17
+ function readServerApiURL(runtimeConfig) {
18
+ const options = runtimeConfig.public.authup;
19
+ let url;
20
+ if (options.apiURLRuntimeKey) {
21
+ const pathValue = getPathValue(runtimeConfig, options.apiURLRuntimeKey);
22
+ if (typeof pathValue === "string") {
23
+ url = pathValue;
24
+ }
25
+ }
26
+ const serverOptions = runtimeConfig.authup;
27
+ if (serverOptions?.serverApiURL) {
28
+ url = serverOptions.serverApiURL;
29
+ }
30
+ return url;
31
+ }
32
+ export function buildHTTPClientOptions(runtimeConfig, isServer) {
33
+ const baseURL = readPublicApiURL(runtimeConfig);
34
+ if (!isServer) {
35
+ return { baseURL };
36
+ }
37
+ const serverURL = readServerApiURL(runtimeConfig);
38
+ if (!serverURL || serverURL === baseURL) {
39
+ return { baseURL };
40
+ }
41
+ const transport = createRebasedTransport(baseURL, serverURL);
42
+ return {
43
+ baseURL,
44
+ httpClient: () => new Client({ baseURL, transport })
45
+ };
46
+ }
@@ -5,7 +5,6 @@ import {
5
5
  LOCALE_UNSET,
6
6
  install
7
7
  } from "@authup/client-web-kit";
8
- import { API_URL_DEFAULT } from "@authup/core-http-kit";
9
8
  import { getPathValue } from "pathtrace";
10
9
  import { computed } from "vue";
11
10
  import {
@@ -15,6 +14,7 @@ import {
15
14
  useRequestURL,
16
15
  useRuntimeConfig
17
16
  } from "#imports";
17
+ import { buildHTTPClientOptions } from "../helpers/http-client.js";
18
18
  function buildPreferenceRef(name, unset, options) {
19
19
  const cookie = useCookie(name, {
20
20
  path: "/",
@@ -64,45 +64,18 @@ function buildCookieName(runtimeConfig) {
64
64
  }
65
65
  return (key) => `${options.cookiePrefix}${key}`;
66
66
  }
67
- function buildApiUrl(runtimeConfig) {
68
- const options = runtimeConfig.public.authup;
69
- let url;
70
- if (import.meta.server) {
71
- if (options.apiURLRuntimeKey) {
72
- const pathValue = getPathValue(runtimeConfig, options.apiURLRuntimeKey);
73
- if (typeof pathValue === "string") {
74
- url = pathValue;
75
- }
76
- }
77
- const serverOptions = runtimeConfig.authup;
78
- if (serverOptions?.serverApiURL) {
79
- url = serverOptions.serverApiURL;
80
- }
81
- }
82
- if (!url) {
83
- if (options.apiURL) {
84
- url = options.apiURL;
85
- }
86
- if (options.apiURLRuntimeKey) {
87
- const pathValue = getPathValue(runtimeConfig.public, options.apiURLRuntimeKey);
88
- if (typeof pathValue === "string") {
89
- url = pathValue;
90
- }
91
- }
92
- }
93
- return url || API_URL_DEFAULT;
94
- }
95
67
  export default defineNuxtPlugin({
96
68
  name: "authup:kit",
97
69
  dependsOn: ["pinia"],
98
70
  setup(ctx) {
99
71
  const runtimeConfig = useRuntimeConfig();
100
- const baseURL = buildApiUrl(runtimeConfig);
72
+ const { baseURL, httpClient } = buildHTTPClientOptions(runtimeConfig, import.meta.server);
101
73
  const cookieOptions = buildCookieOptions(runtimeConfig);
102
74
  const cookieName = buildCookieName(runtimeConfig);
103
75
  install(ctx.vueApp, {
104
76
  pinia: ctx.$pinia,
105
77
  baseURL,
78
+ httpClient,
106
79
  // A refresh during the server render rotates the token cookies
107
80
  // too, and the kit cannot see the scheme without a window.
108
81
  cookieSecure: useRequestURL().protocol === "https:",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@authup/client-web-nuxt",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.69",
5
- "description": "Nuxt module for the authup ecosystem",
4
+ "version": "1.0.0-beta.71",
5
+ "description": "Nuxt module that signs your app in with Authup and guards its routes by login state and permission.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -32,32 +32,32 @@
32
32
  "node": "^22.13.0 || ^23.5.0 || >=24.0.0"
33
33
  },
34
34
  "dependencies": {
35
- "@authup/access": "^1.0.0-beta.69",
36
- "@authup/client-web-kit": "^1.0.0-beta.69",
37
- "@authup/core-http-kit": "^1.0.0-beta.69",
38
- "@authup/kit": "^1.0.0-beta.69",
35
+ "@authup/access": "^1.0.0-beta.71",
36
+ "@authup/client-web-kit": "^1.0.0-beta.71",
37
+ "@authup/core-http-kit": "^1.0.0-beta.71",
38
+ "@authup/kit": "^1.0.0-beta.71",
39
39
  "@nuxt/kit": "^4.6.1",
40
40
  "pathtrace": "^2.2.0",
41
41
  "smob": "^1.6.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@nuxt/module-builder": "^1.0.3",
45
- "@nuxt/schema": "^4.6.1",
46
- "@pinia/nuxt": "^1.0.2",
45
+ "@nuxt/schema": "^4.6.2",
46
+ "@pinia/nuxt": "^1.0.3",
47
47
  "cross-env": "^10.1.0",
48
48
  "fix-dts-default-cjs-exports": "^1.0.1",
49
49
  "mkdist": "^2.3.0",
50
- "nuxt": "^4.6.1",
51
- "pinia": "^4.0.3",
50
+ "nuxt": "^4.6.2",
51
+ "pinia": "^4.1.0",
52
52
  "rollup-plugin-dts": "^6.5.1",
53
53
  "strip-literal": "^4.0.0",
54
54
  "vitest": "^5.0.3",
55
- "vue": "^3.5.43",
55
+ "vue": "^3.5.44",
56
56
  "vue-router": "^5.4.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@pinia/nuxt": "*",
60
- "pinia": "^4.0.3"
60
+ "pinia": "^4.1.0"
61
61
  },
62
62
  "keywords": [
63
63
  "auth",