@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 +166 -0
- package/dist/module.json +1 -1
- package/dist/runtime/helpers/http-client.d.ts +15 -0
- package/dist/runtime/helpers/http-client.js +46 -0
- package/dist/runtime/plugins/kit.js +3 -30
- package/package.json +12 -12
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
|
@@ -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 =
|
|
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.
|
|
5
|
-
"description": "Nuxt module
|
|
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.
|
|
36
|
-
"@authup/client-web-kit": "^1.0.0-beta.
|
|
37
|
-
"@authup/core-http-kit": "^1.0.0-beta.
|
|
38
|
-
"@authup/kit": "^1.0.0-beta.
|
|
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.
|
|
46
|
-
"@pinia/nuxt": "^1.0.
|
|
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.
|
|
51
|
-
"pinia": "^4.0
|
|
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.
|
|
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
|
|
60
|
+
"pinia": "^4.1.0"
|
|
61
61
|
},
|
|
62
62
|
"keywords": [
|
|
63
63
|
"auth",
|