@authup/client-web-nuxt 1.0.0-beta.68 → 1.0.0-beta.70
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/routing-interceptor.js +12 -13
- package/dist/runtime/plugins/kit.js +8 -4
- package/package.json +15 -15
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`.
|
|
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` | API URL for the server render. 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
|
@@ -31,20 +31,19 @@ export class RoutingInterceptor {
|
|
|
31
31
|
const { redirect } = to.query;
|
|
32
32
|
const destination = typeof redirect === "string" ? this.resolveDestination(redirect) : void 0;
|
|
33
33
|
try {
|
|
34
|
-
if (request) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
code_verifier: request.code_verifier,
|
|
41
|
-
redirect_uri: request.redirect_uri,
|
|
42
|
-
client_id: request.client_id,
|
|
43
|
-
realm_id: request.realm_id
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
await this.store.exchangeAuthorizationCode(code);
|
|
34
|
+
if (!request) {
|
|
35
|
+
throw new Error("No authorization request is pending.");
|
|
36
|
+
}
|
|
37
|
+
const state = typeof to.query.state === "string" ? to.query.state : void 0;
|
|
38
|
+
if (request.state !== state) {
|
|
39
|
+
throw new Error("The authorization request state does not match.");
|
|
47
40
|
}
|
|
41
|
+
await this.store.exchangeAuthorizationCode(code, {
|
|
42
|
+
code_verifier: request.code_verifier,
|
|
43
|
+
redirect_uri: request.redirect_uri,
|
|
44
|
+
client_id: request.client_id,
|
|
45
|
+
realm_id: request.realm_id
|
|
46
|
+
});
|
|
48
47
|
clearAuthorizationRequest();
|
|
49
48
|
if (destination) {
|
|
50
49
|
const query = {};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
defineNuxtPlugin,
|
|
13
13
|
tryUseNuxtApp,
|
|
14
14
|
useCookie,
|
|
15
|
+
useRequestURL,
|
|
15
16
|
useRuntimeConfig
|
|
16
17
|
} from "#imports";
|
|
17
18
|
function buildPreferenceRef(name, unset, options) {
|
|
@@ -102,12 +103,15 @@ export default defineNuxtPlugin({
|
|
|
102
103
|
install(ctx.vueApp, {
|
|
103
104
|
pinia: ctx.$pinia,
|
|
104
105
|
baseURL,
|
|
106
|
+
// A refresh during the server render rotates the token cookies
|
|
107
|
+
// too, and the kit cannot see the scheme without a window.
|
|
108
|
+
cookieSecure: useRequestURL().protocol === "https:",
|
|
105
109
|
cookieSet: (key, value, options) => {
|
|
106
110
|
const app = tryUseNuxtApp();
|
|
107
111
|
if (app) {
|
|
108
112
|
const cookie = useCookie(cookieName(key), {
|
|
109
|
-
...
|
|
110
|
-
...
|
|
113
|
+
...options || {},
|
|
114
|
+
...cookieOptions
|
|
111
115
|
});
|
|
112
116
|
cookie.value = value;
|
|
113
117
|
}
|
|
@@ -116,8 +120,8 @@ export default defineNuxtPlugin({
|
|
|
116
120
|
const app = tryUseNuxtApp();
|
|
117
121
|
if (app) {
|
|
118
122
|
const cookie = useCookie(cookieName(key), {
|
|
119
|
-
...
|
|
120
|
-
...
|
|
123
|
+
...options || {},
|
|
124
|
+
...cookieOptions
|
|
121
125
|
});
|
|
122
126
|
cookie.value = null;
|
|
123
127
|
}
|
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.70",
|
|
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.
|
|
39
|
-
"@nuxt/kit": "^4.
|
|
35
|
+
"@authup/access": "^1.0.0-beta.70",
|
|
36
|
+
"@authup/client-web-kit": "^1.0.0-beta.70",
|
|
37
|
+
"@authup/core-http-kit": "^1.0.0-beta.70",
|
|
38
|
+
"@authup/kit": "^1.0.0-beta.70",
|
|
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.
|
|
46
|
-
"@pinia/nuxt": "^1.0.
|
|
45
|
+
"@nuxt/schema": "^4.6.1",
|
|
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.
|
|
51
|
-
"pinia": "^4.0
|
|
50
|
+
"nuxt": "^4.6.1",
|
|
51
|
+
"pinia": "^4.1.0",
|
|
52
52
|
"rollup-plugin-dts": "^6.5.1",
|
|
53
53
|
"strip-literal": "^4.0.0",
|
|
54
|
-
"vitest": "^
|
|
55
|
-
"vue": "^3.5.
|
|
56
|
-
"vue-router": "^5.
|
|
54
|
+
"vitest": "^5.0.3",
|
|
55
|
+
"vue": "^3.5.44",
|
|
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",
|