nuxt-bearer-auth 0.1.6 → 0.1.8
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 +141 -0
- package/dist/module.d.mts +128 -144
- package/dist/module.d.ts +128 -144
- package/dist/module.json +1 -1
- package/dist/module.mjs +58 -12
- package/dist/runtime/components/Can.vue +23 -0
- package/dist/runtime/components/Cannot.vue +24 -0
- package/dist/runtime/components/useAbilityRequirement.d.ts +40 -0
- package/dist/runtime/components/useAbilityRequirement.js +22 -0
- package/dist/runtime/composables/useBearerAuth.d.ts +9 -11
- package/dist/runtime/composables/useBearerAuth.js +80 -31
- package/dist/runtime/middleware/bearer-auth.global.d.ts +1 -1
- package/dist/runtime/middleware/bearer-auth.global.js +9 -0
- package/dist/runtime/plugins/bearer-auth.server.d.ts +1 -1
- package/dist/runtime/plugins/bearer-auth.server.js +5 -2
- package/dist/runtime/server/api/auth/login.post.d.ts +2 -1
- package/dist/runtime/server/api/auth/login.post.js +5 -1
- package/dist/runtime/server/api/auth/me.get.d.ts +1 -0
- package/dist/runtime/server/api/auth/me.get.js +10 -3
- package/dist/runtime/server/api/auth/otp-verification.post.d.ts +3 -2
- package/dist/runtime/server/api/auth/otp-verification.post.js +5 -1
- package/dist/runtime/server/api/auth/refresh.post.d.ts +2 -1
- package/dist/runtime/server/api/auth/refresh.post.js +9 -2
- package/dist/runtime/server/api/auth/register.post.d.ts +3 -2
- package/dist/runtime/server/api/auth/register.post.js +5 -1
- package/dist/runtime/server/api/auth/social-login.post.d.ts +2 -1
- package/dist/runtime/server/api/auth/social-login.post.js +5 -1
- package/dist/runtime/server/middleware/auth.js +9 -0
- package/dist/runtime/server/utils/authorization.d.ts +26 -0
- package/dist/runtime/server/utils/authorization.js +97 -0
- package/dist/runtime/server/utils/config.d.ts +2 -1
- package/dist/runtime/server/utils/sessions.d.ts +3 -2
- package/dist/runtime/server/utils/sessions.js +6 -1
- package/dist/runtime/types/auth.d.ts +6 -0
- package/dist/runtime/types/h3.d.ts +2 -1
- package/dist/runtime/utils/abilities.d.ts +26 -0
- package/dist/runtime/utils/abilities.js +16 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ Reusable Nuxt authentication for APIs that issue bearer tokens. It is designed f
|
|
|
21
21
|
- Session revocation
|
|
22
22
|
- SSR auth hydration
|
|
23
23
|
- Global route middleware
|
|
24
|
+
- Optional authorization with abilities normalization (disabled by default)
|
|
25
|
+
- Route/page authorization via `definePageMeta` metadata (`all` / `any` modes)
|
|
26
|
+
- Declarative `<Can>` / `<Cannot>` UI authorization components (advisory rendering only)
|
|
27
|
+
- Server-side `requireAbility()` guard via the `nuxt-bearer-auth/server` export
|
|
24
28
|
- `useBearerAuth()` composable
|
|
25
29
|
- `useAuth()` alias for convenience
|
|
26
30
|
|
|
@@ -180,8 +184,145 @@ Available composable state and methods:
|
|
|
180
184
|
- `forgotPassword`
|
|
181
185
|
- `resetPassword`
|
|
182
186
|
- `resendOtp`
|
|
187
|
+
- `abilities`
|
|
188
|
+
- `can(ability)`
|
|
189
|
+
- `cannot(ability)`
|
|
183
190
|
- `clearAuthState`
|
|
184
191
|
|
|
192
|
+
## Authorization
|
|
193
|
+
|
|
194
|
+
Authorization is opt-in and disabled by default. Backend roles, permissions, and direct abilities are normalized into a sorted `string[]`. Roles use the `role:` prefix by default.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
export default defineNuxtConfig({
|
|
198
|
+
bearerAuth: {
|
|
199
|
+
authorization: {
|
|
200
|
+
enabled: true,
|
|
201
|
+
source: "session",
|
|
202
|
+
responsePaths: {
|
|
203
|
+
abilities: ["abilities", "data.abilities"],
|
|
204
|
+
roles: ["roles", "data.roles"],
|
|
205
|
+
permissions: ["permissions", "data.permissions"],
|
|
206
|
+
},
|
|
207
|
+
rolePrefix: "role:",
|
|
208
|
+
},
|
|
209
|
+
redirects: {
|
|
210
|
+
unauthorized: "/auth/not-allowed",
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const auth = useBearerAuth();
|
|
218
|
+
|
|
219
|
+
await auth.login(credentials);
|
|
220
|
+
auth.can("campaign.create");
|
|
221
|
+
auth.cannot("campaign.delete");
|
|
222
|
+
auth.abilities.value;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Abilities are persisted with the server session and hydrated during SSR. Login, social login, registration, OTP verification, refresh, and `me` responses can update them. An omitted authorization field preserves the current list; logout and failed authentication clear it. Tokens, passwords, OTPs, and raw backend responses are never authorization state.
|
|
226
|
+
|
|
227
|
+
### Client Checks Are Not Security
|
|
228
|
+
|
|
229
|
+
`auth.can()` and `auth.cannot()` are advisory UI helpers for showing or hiding controls. They do not secure APIs. A user who bypasses your UI can still call any endpoint directly, so your backend must authorize every request it receives.
|
|
230
|
+
|
|
231
|
+
### UI Authorization Components
|
|
232
|
+
|
|
233
|
+
`<Can>` and `<Cannot>` provide the same advisory checks declaratively. Both are auto-imported, evaluate the existing `auth.abilities` state reactively through one shared evaluator, and match route/server semantics exactly (exact strings, `mode` defaults to `"all"`, empty `abilities` means unrestricted, malformed props or state fail closed):
|
|
234
|
+
|
|
235
|
+
```vue
|
|
236
|
+
<template>
|
|
237
|
+
<!-- single ability -->
|
|
238
|
+
<Can ability="users.delete">
|
|
239
|
+
Delete
|
|
240
|
+
</Can>
|
|
241
|
+
|
|
242
|
+
<!-- every listed ability (default) / at least one -->
|
|
243
|
+
<Can :abilities="['users.view', 'users.edit']">Edit</Can>
|
|
244
|
+
<Can :abilities="['reports.view', 'reports.export']" mode="any">
|
|
245
|
+
Export
|
|
246
|
+
</Can>
|
|
247
|
+
|
|
248
|
+
<!-- inverse rendering -->
|
|
249
|
+
<Cannot ability="users.delete">
|
|
250
|
+
<template #fallback>Request access</template>
|
|
251
|
+
</Cannot>
|
|
252
|
+
</template>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Unauthorized content is removed from the DOM; an optional `#fallback` slot renders instead. Disabled authorization behaves like `auth.can()` — ability state stays `null`, so `<Can>` renders fallback/nothing. SSR output matches hydration because evaluation is synchronous over the transferred state.
|
|
256
|
+
|
|
257
|
+
As with all client checks: **UI authorization controls rendering only. It never secures API requests. Your backend must authorize every request it receives.**
|
|
258
|
+
|
|
259
|
+
### Route Authorization
|
|
260
|
+
|
|
261
|
+
Pages opt in by declaring required abilities in `definePageMeta`. The global `bearer-auth` middleware enforces them when authorization is enabled:
|
|
262
|
+
|
|
263
|
+
```vue
|
|
264
|
+
<script setup lang="ts">
|
|
265
|
+
definePageMeta({
|
|
266
|
+
authorization: {
|
|
267
|
+
abilities: ["users.view"],
|
|
268
|
+
// optional — defaults to "all"
|
|
269
|
+
mode: "all",
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
</script>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Semantics:
|
|
276
|
+
|
|
277
|
+
- `mode` defaults to `"all"`: every listed ability must exist on the session.
|
|
278
|
+
- `mode: "any"`: at least one listed ability must exist.
|
|
279
|
+
- Matching is exact string equality. `users.*`, prefixes, and hierarchy are not supported.
|
|
280
|
+
- Routes without metadata behave exactly as before. When `authorization.enabled` is `false`, metadata is ignored.
|
|
281
|
+
- Unauthenticated visitors follow the normal login flow (`?redirect=` preserved). Authenticated users without the required abilities are redirected to `redirects.unauthorized` — not to login.
|
|
282
|
+
|
|
283
|
+
Route authorization protects navigation inside your Nuxt app only. It never authorizes external API requests.
|
|
284
|
+
|
|
285
|
+
### Server Enforcement with requireAbility()
|
|
286
|
+
|
|
287
|
+
Nitro server routes use the dedicated server-only export:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { requireAbility } from "nuxt-bearer-auth/server";
|
|
291
|
+
|
|
292
|
+
export default defineEventHandler((event) => {
|
|
293
|
+
const session = requireAbility(event, "users.delete");
|
|
294
|
+
|
|
295
|
+
// every ability required (default):
|
|
296
|
+
requireAbility(event, ["users.view", "users.export"]);
|
|
297
|
+
// at least one required:
|
|
298
|
+
requireAbility(event, ["reports.view", "reports.export"], "any");
|
|
299
|
+
|
|
300
|
+
return { ok: true };
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Behavior:
|
|
305
|
+
|
|
306
|
+
- Throws `401 Unauthenticated` when there is no authenticated session and `403 Authorization required` when the session lacks the ability.
|
|
307
|
+
- Reads abilities only from `event.context.auth` — the Redis-backed server session. It never reads client state (`useState("bearer-auth-abilities")`), headers, query parameters, or request bodies.
|
|
308
|
+
- On success it sets `event.context.authorization = { abilities, source: "session" }` (normalized strings only, no tokens) and returns the session.
|
|
309
|
+
- Fails closed: if the session has no abilities, every requirement is rejected.
|
|
310
|
+
- The helper lives behind the `nuxt-bearer-auth/server` subpath so it is never bundled into client code.
|
|
311
|
+
|
|
312
|
+
### Security Boundary
|
|
313
|
+
|
|
314
|
+
Authorization is layered, and the lower layer is always authoritative:
|
|
315
|
+
|
|
316
|
+
```text
|
|
317
|
+
UI checks (auth.can) → convenience only
|
|
318
|
+
Nuxt enforcement (meta/403) → protects pages and Nuxt routes
|
|
319
|
+
Laravel backend → authoritative for its own endpoints
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`requireAbility(event, "campaign.delete")` stops your Nuxt route from running, but if that route calls `DELETE /campaigns/123`, Laravel must still authorize `campaign.delete`. This package never claims to replace backend authorization.
|
|
323
|
+
|
|
324
|
+
Declarative `<Can>` / `<Cannot>` components ship with the package — see [UI Authorization Components](#ui-authorization-components). Directives such as `v-can` are intentionally not provided.
|
|
325
|
+
|
|
185
326
|
## Redis Sessions
|
|
186
327
|
|
|
187
328
|
The backend token is stored server-side in Redis. The browser only receives a secure HTTP-only session id cookie. Nuxt server routes can read the hydrated session from:
|
package/dist/module.d.mts
CHANGED
|
@@ -1,146 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
addServerHandler({
|
|
129
|
-
route: `${prefix}/register`,
|
|
130
|
-
method: "post",
|
|
131
|
-
handler: resolver.resolve("runtime/server/api/auth/register.post")
|
|
132
|
-
});
|
|
133
|
-
addServerHandler({
|
|
134
|
-
route: `${prefix}/sessions`,
|
|
135
|
-
method: "get",
|
|
136
|
-
handler: resolver.resolve("runtime/server/api/auth/sessions.get")
|
|
137
|
-
});
|
|
138
|
-
addServerHandler({
|
|
139
|
-
route: `${prefix}/sessions/:id`,
|
|
140
|
-
method: "delete",
|
|
141
|
-
handler: resolver.resolve("runtime/server/api/auth/sessions/[id].delete")
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
});
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
export * from '../dist/runtime/types/auth.js';
|
|
3
|
+
|
|
4
|
+
type SameSite = true | false | "lax" | "strict" | "none";
|
|
5
|
+
interface BearerAuthEndpointOptions {
|
|
6
|
+
login: string;
|
|
7
|
+
socialLogin: string;
|
|
8
|
+
logout: string;
|
|
9
|
+
me: string;
|
|
10
|
+
refresh: string;
|
|
11
|
+
forgotPassword: string;
|
|
12
|
+
resetPassword: string;
|
|
13
|
+
verifyOtp: string;
|
|
14
|
+
resendOtp: string;
|
|
15
|
+
register: string;
|
|
16
|
+
}
|
|
17
|
+
interface BearerAuthResponsePaths {
|
|
18
|
+
token: string[];
|
|
19
|
+
refreshToken: string[];
|
|
20
|
+
user: string[];
|
|
21
|
+
userId: string[];
|
|
22
|
+
message: string[];
|
|
23
|
+
success: string[];
|
|
24
|
+
code: string[];
|
|
25
|
+
nextAction: string[];
|
|
26
|
+
}
|
|
27
|
+
interface BearerAuthRedirectOptions {
|
|
28
|
+
login: string;
|
|
29
|
+
authenticated: string;
|
|
30
|
+
logout: string;
|
|
31
|
+
unauthorized: string;
|
|
32
|
+
}
|
|
33
|
+
interface BearerAuthRouteOptions {
|
|
34
|
+
localApiPrefix: string;
|
|
35
|
+
public: string[];
|
|
36
|
+
authPages: string[];
|
|
37
|
+
protectedApiPrefixes: string[];
|
|
38
|
+
publicApiPrefixes: string[];
|
|
39
|
+
middleware: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface BearerAuthCookieOptions {
|
|
42
|
+
name: string;
|
|
43
|
+
devName: string;
|
|
44
|
+
maxAge: number;
|
|
45
|
+
sameSite: SameSite;
|
|
46
|
+
secure?: boolean;
|
|
47
|
+
domain?: string;
|
|
48
|
+
path: string;
|
|
49
|
+
}
|
|
50
|
+
interface BearerAuthCsrfOptions {
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
https: boolean;
|
|
53
|
+
cookieKey: string;
|
|
54
|
+
devCookieKey: string;
|
|
55
|
+
headerName: string;
|
|
56
|
+
methods: string[];
|
|
57
|
+
methodsToProtect: string[];
|
|
58
|
+
cookie: {
|
|
59
|
+
path: string;
|
|
60
|
+
httpOnly: boolean;
|
|
61
|
+
sameSite: SameSite;
|
|
62
|
+
secure?: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
type Ability = string;
|
|
66
|
+
type AuthorizationSource = "session" | "endpoint";
|
|
67
|
+
type AuthorizationMatchMode = "all" | "any";
|
|
68
|
+
interface AuthorizationRouteRequirement {
|
|
69
|
+
abilities: string[];
|
|
70
|
+
mode?: AuthorizationMatchMode;
|
|
71
|
+
}
|
|
72
|
+
interface AuthorizationResponsePaths {
|
|
73
|
+
roles?: string[];
|
|
74
|
+
permissions?: string[];
|
|
75
|
+
abilities?: string[];
|
|
76
|
+
}
|
|
77
|
+
interface BearerAuthAuthorizationConfig {
|
|
78
|
+
enabled: boolean;
|
|
79
|
+
source: AuthorizationSource;
|
|
80
|
+
endpoint?: string;
|
|
81
|
+
responsePaths: AuthorizationResponsePaths;
|
|
82
|
+
rolePrefix: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Shape of the private (server-side) `runtimeConfig.bearerAuth` namespace
|
|
86
|
+
* populated by the module. Never place functions or secrets beyond what the
|
|
87
|
+
* server requires here; runtime configuration must stay serializable.
|
|
88
|
+
*/
|
|
89
|
+
interface BearerAuthPrivateRuntimeConfig {
|
|
90
|
+
apiBaseUrl: string;
|
|
91
|
+
redisUrl: string;
|
|
92
|
+
sessionSecret: string;
|
|
93
|
+
appEnv: string;
|
|
94
|
+
endpoints: BearerAuthEndpointOptions;
|
|
95
|
+
responsePaths: BearerAuthResponsePaths;
|
|
96
|
+
sessionCookie: BearerAuthCookieOptions;
|
|
97
|
+
authorization: BearerAuthAuthorizationConfig;
|
|
98
|
+
verificationRequiredActions: string[];
|
|
99
|
+
twoFactorRequiredActions: string[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Shape of the public `runtimeConfig.public.bearerAuth` namespace.
|
|
103
|
+
* Only serializable, browser-safe values are allowed here.
|
|
104
|
+
*/
|
|
105
|
+
interface BearerAuthPublicRuntimeConfig {
|
|
106
|
+
redirects: Required<BearerAuthRedirectOptions>;
|
|
107
|
+
routes: Required<BearerAuthRouteOptions>;
|
|
108
|
+
authorizationEnabled: boolean;
|
|
109
|
+
}
|
|
110
|
+
interface BearerAuthModuleOptions {
|
|
111
|
+
apiBaseUrl?: string;
|
|
112
|
+
redisUrl?: string;
|
|
113
|
+
sessionSecret?: string;
|
|
114
|
+
appEnv?: string;
|
|
115
|
+
installCsurf?: boolean;
|
|
116
|
+
endpoints?: Partial<BearerAuthEndpointOptions>;
|
|
117
|
+
responsePaths?: Partial<BearerAuthResponsePaths>;
|
|
118
|
+
redirects?: Partial<BearerAuthRedirectOptions>;
|
|
119
|
+
routes?: Partial<BearerAuthRouteOptions>;
|
|
120
|
+
sessionCookie?: Partial<BearerAuthCookieOptions>;
|
|
121
|
+
csrf?: Partial<BearerAuthCsrfOptions>;
|
|
122
|
+
authorization?: Partial<BearerAuthAuthorizationConfig>;
|
|
123
|
+
verificationRequiredActions?: string[];
|
|
124
|
+
twoFactorRequiredActions?: string[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare const module: _nuxt_schema.NuxtModule<BearerAuthModuleOptions, BearerAuthModuleOptions, false>;
|
|
145
128
|
|
|
146
129
|
export { module as default };
|
|
130
|
+
export type { Ability, AuthorizationMatchMode, AuthorizationResponsePaths, AuthorizationRouteRequirement, AuthorizationSource, BearerAuthAuthorizationConfig, BearerAuthCookieOptions, BearerAuthCsrfOptions, BearerAuthEndpointOptions, BearerAuthModuleOptions, BearerAuthPrivateRuntimeConfig, BearerAuthPublicRuntimeConfig, BearerAuthRedirectOptions, BearerAuthResponsePaths, BearerAuthRouteOptions, SameSite };
|