@devcoffee/nuxt-core 1.0.2 → 1.1.1
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/CHANGELOG.md +82 -0
- package/GUIDELINE.md +351 -0
- package/README.md +405 -84
- package/dist/module.d.mts +295 -156
- package/dist/module.d.ts +300 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +191 -36
- package/dist/runtime/app/composables/useAuthContext.d.ts +26 -0
- package/dist/runtime/app/composables/useAuthContext.js +111 -0
- package/dist/runtime/app/composables/useLogger.d.ts +3 -3
- package/dist/runtime/app/composables/useSessionContext.d.ts +22 -0
- package/dist/runtime/app/composables/useSessionContext.js +5 -0
- package/dist/runtime/app/middleware/authts.d.ts +12 -0
- package/dist/runtime/app/middleware/authts.js +101 -0
- package/dist/runtime/app/pages/authorize.d.vue.ts +3 -0
- package/dist/runtime/app/pages/authorize.vue +32 -0
- package/dist/runtime/app/pages/authorize.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/authts.d.ts +82 -0
- package/dist/runtime/app/plugins/authts.js +91 -0
- package/dist/runtime/app/plugins/formatters.d.ts +29 -0
- package/dist/runtime/app/plugins/formatters.js +101 -0
- package/dist/runtime/app/plugins/locale.d.ts +37 -0
- package/dist/runtime/app/plugins/locale.js +39 -0
- package/dist/runtime/app/plugins/logging.d.ts +24 -16
- package/dist/runtime/app/plugins/logging.js +0 -1
- package/dist/runtime/app/utils/hashing.d.ts +1 -0
- package/dist/runtime/app/utils/hashing.js +3 -0
- package/dist/runtime/server/adapters/http.d.ts +5 -0
- package/dist/runtime/server/adapters/http.js +15 -0
- package/dist/runtime/server/adapters/oidc.d.ts +58 -0
- package/dist/runtime/server/adapters/oidc.js +21 -0
- package/dist/runtime/server/adapters/storage.d.ts +39 -0
- package/dist/runtime/server/adapters/storage.js +14 -0
- package/dist/runtime/server/adapters/utils.d.ts +31 -0
- package/dist/runtime/server/adapters/utils.js +28 -0
- package/dist/runtime/server/composables/useServerLogger.d.ts +3 -2
- package/dist/runtime/server/composables/useServerLogger.js +4 -4
- package/dist/runtime/server/core/crypto.d.ts +70 -0
- package/dist/runtime/server/core/crypto.js +55 -0
- package/dist/runtime/server/core/helpers.d.ts +194 -0
- package/dist/runtime/server/core/helpers.js +350 -0
- package/dist/runtime/server/core/index.d.ts +1 -0
- package/dist/runtime/server/core/index.js +1 -0
- package/dist/runtime/server/core/mutex.d.ts +19 -0
- package/dist/runtime/server/core/mutex.js +39 -0
- package/dist/runtime/server/core/nuxtAuthtsHandler.d.ts +26 -0
- package/dist/runtime/server/core/nuxtAuthtsHandler.js +238 -0
- package/dist/runtime/server/core/nuxtForwardHandler.d.ts +18 -0
- package/dist/runtime/server/core/nuxtForwardHandler.js +60 -0
- package/dist/runtime/server/dev/route/session.d.ts +2 -0
- package/dist/runtime/server/dev/route/session.js +8 -0
- package/dist/runtime/server/plugins/authts.d.ts +11 -0
- package/dist/runtime/server/plugins/authts.js +55 -0
- package/dist/runtime/server/plugins/logging.js +7 -2
- package/dist/runtime/server/tsconfig.json +3 -3
- package/dist/runtime/types/global.env.d.ts +21 -7
- package/dist/runtime/types/nitro.d.ts +7 -2
- package/dist/runtime/types/nuxt.d.ts +28 -8
- package/dist/runtime/utils.d.ts +31 -0
- package/dist/runtime/utils.js +28 -0
- package/dist/types.d.mts +6 -4
- package/package.json +45 -17
- package/dist/runtime/plugin.d.ts +0 -2
- package/dist/runtime/plugin.js +0 -4
package/README.md
CHANGED
|
@@ -1,84 +1,405 @@
|
|
|
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
|
-
|
|
1
|
+
# @devcoffee/nuxt-core
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
Full OpenID Connect / OAuth 2.0 authorization code grant with PKCE for DevCoffee internal Nuxt 4 applications.
|
|
9
|
+
Provides server-side session management via Nitro, client-side auth state via Vue composables, and universal route protection middleware.
|
|
10
|
+
|
|
11
|
+
- [Release Notes](/CHANGELOG.md)
|
|
12
|
+
- [Contributor Guide](/GUIDELINE.md)
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- PKCE-enforced authorization code flow (S256, enabled by default)
|
|
17
|
+
- HMAC-SHA256 signed session cookies when `sessions.secret` is configured
|
|
18
|
+
- AES-256-GCM encrypted tokenSet storage with HKDF-derived key
|
|
19
|
+
- 256-bit session ID entropy via `crypto.randomBytes(32)`
|
|
20
|
+
- Open redirect protection on all post-auth redirects
|
|
21
|
+
- Per-route protection via `definePageMeta` — no page-level boilerplate
|
|
22
|
+
- Auto-imported composables: `useAuthContext`, `useSessionContext`, `useLogger`
|
|
23
|
+
- Server-side session validation on every Nitro request
|
|
24
|
+
- Token refresh mutex — no concurrent refresh races
|
|
25
|
+
- User info caching with configurable TTL
|
|
26
|
+
- Nuxt DevTools integration (session inspector)
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- **Nuxt:** `^4.0.0` — Nuxt 3 is not supported
|
|
31
|
+
- **Node.js:** LTS (18+)
|
|
32
|
+
- **OIDC provider:** Any provider with a `.well-known/openid-configuration` endpoint
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @devcoffee/nuxt-core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Register the module in `nuxt.config.ts`:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
export default defineNuxtConfig({
|
|
44
|
+
modules: ['@devcoffee/nuxt-core'],
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Setup
|
|
49
|
+
|
|
50
|
+
**1. Install and register the module** (see [Installation](#installation) above).
|
|
51
|
+
|
|
52
|
+
**2. Add the minimum config to `nuxt.config.ts`:**
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
export default defineNuxtConfig({
|
|
56
|
+
modules: ['@devcoffee/nuxt-core'],
|
|
57
|
+
nuxtCore: {
|
|
58
|
+
authts: {
|
|
59
|
+
openid: {
|
|
60
|
+
wellKnownUrl: process.env.OIDC_WELL_KNOWN_URL!,
|
|
61
|
+
clientId: process.env.OIDC_CLIENT_ID!,
|
|
62
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
63
|
+
redirectUri: '/authorize',
|
|
64
|
+
scopes: ['openid', 'profile', 'email'],
|
|
65
|
+
},
|
|
66
|
+
sessions: {
|
|
67
|
+
secret: process.env.SESSION_SECRET!,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**3. Create the server handler** — **required**. Auth endpoints return 404 without this file.
|
|
75
|
+
|
|
76
|
+
Create `server/api/_auth/[...].ts`:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
export default NuxtAuthtsHandler({
|
|
80
|
+
userInfo: async (user, { openidUser }) => ({
|
|
81
|
+
...user,
|
|
82
|
+
id: openidUser?.sub ?? user.id,
|
|
83
|
+
email: openidUser?.email ?? user.email,
|
|
84
|
+
firstName: openidUser?.given_name ?? user.firstName,
|
|
85
|
+
lastName: openidUser?.family_name ?? user.lastName,
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> The OIDC callback page at `openid.redirectUri` (default `/authorize`) is auto-registered by the module. Do NOT create it manually.
|
|
91
|
+
|
|
92
|
+
**4. Set environment variables:**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
OIDC_WELL_KNOWN_URL=https://your-provider/.well-known/openid-configuration
|
|
96
|
+
OIDC_CLIENT_ID=your-client-id
|
|
97
|
+
OIDC_CLIENT_SECRET=your-client-secret
|
|
98
|
+
SESSION_SECRET=your-32-char-minimum-random-secret
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Configuration Reference
|
|
102
|
+
|
|
103
|
+
All options are nested under `nuxtCore` in `nuxt.config.ts`.
|
|
104
|
+
|
|
105
|
+
### `authts.openid` options
|
|
106
|
+
|
|
107
|
+
| Option | Type | Default | Description |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `wellKnownUrl` | `string` | `''` (required) | OIDC provider discovery URL (`/.well-known/openid-configuration` endpoint) |
|
|
110
|
+
| `clientId` | `string` | `''` (required) | OAuth 2.0 client ID registered with the OIDC provider |
|
|
111
|
+
| `clientSecret` | `string` | `''` (required) | OAuth 2.0 client secret |
|
|
112
|
+
| `redirectUri` | `string` | `'/authorize'` | OIDC callback path. Must match the redirect URI registered with your provider. The module auto-registers this page. |
|
|
113
|
+
| `scopes` | `string[]` | `[]` | OAuth scopes to request. Include `'openid'` at minimum. |
|
|
114
|
+
| `usePkce` | `boolean` | `true` | Enable PKCE (S256). Strongly recommended — disabling reduces security. |
|
|
115
|
+
| `codeChallengeMethod` | `string` | `'S256'` | PKCE code challenge method |
|
|
116
|
+
| `autoFetchUser` | `boolean` | `true` | Fetch user info from the OIDC userinfo endpoint on GET_SESSION |
|
|
117
|
+
| `autoFetchUserTtl` | `number` | `300` | Userinfo cache TTL in seconds. Prevents redundant OIDC provider calls. |
|
|
118
|
+
| `fetchUserOnLogin` | `boolean` | `true` | Fetch user info immediately after the token exchange |
|
|
119
|
+
| `tokenRefreshBufferMs` | `number` | `60000` | Refresh tokens this many ms before expiry |
|
|
120
|
+
| `cache.prefix` | `string` | `'oidc-server-meta'` | Nitro cache key prefix for OIDC server metadata |
|
|
121
|
+
| `cache.expires` | `number` | `86400` | OIDC metadata cache TTL in seconds (24 hours) |
|
|
122
|
+
|
|
123
|
+
### `authts.sessions` options
|
|
124
|
+
|
|
125
|
+
| Option | Type | Default | Description |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| `secret` | `string` | `''` | Secret for HMAC-SHA256 cookie signing and AES-256-GCM tokenSet encryption. **Must be set in production** (see [Security](#security)). Empty string disables signing and encryption. |
|
|
128
|
+
| `expiresIn` | `number` | `518400` | Session lifetime in seconds (default 6 days) |
|
|
129
|
+
| `names.sessionId` | `string` | `'auths.ssid'` | Session ID cookie name |
|
|
130
|
+
| `names.state` | `string` | `'auths.state'` | OAuth state cookie name |
|
|
131
|
+
| `names.redirectUrl` | `string` | `'auths.redirect'` | Redirect URL cookie name |
|
|
132
|
+
| `names.pkce` | `string` | `'auths.pkce'` | PKCE verifier cookie name |
|
|
133
|
+
| `storage.name` | `string` | `'sessions'` | Nitro storage mount name |
|
|
134
|
+
| `storage.prefix` | `string` | `'session'` | Nitro storage key prefix |
|
|
135
|
+
| `cookieOpts.path` | `string` | `'/'` | Cookie path |
|
|
136
|
+
| `cookieOpts.sameSite` | `string` | `'lax'` | Cookie SameSite attribute |
|
|
137
|
+
| `cookieOpts.httpOnly` | `boolean` | `true` | Cookie HttpOnly flag |
|
|
138
|
+
| `cookieOpts.secure` | `boolean` | `true` in production | Cookie Secure flag |
|
|
139
|
+
|
|
140
|
+
### `authts.auth` options
|
|
141
|
+
|
|
142
|
+
| Option | Type | Default | Description |
|
|
143
|
+
| --- | --- | --- | --- |
|
|
144
|
+
| `loginUri` | `string` | `'/login'` | Path middleware redirects unauthenticated users to |
|
|
145
|
+
| `defaultLoginRedirectUri` | `string` | `'/'` | Default post-login redirect when no intended destination is recorded |
|
|
146
|
+
| `defaultLogoutRedirectUri` | `string` | `'/login'` | Post-logout redirect |
|
|
147
|
+
| `ignoreRegexPatterns` | `RegExp[]` | `[]` | Routes matching these patterns are excluded from middleware in all environments |
|
|
148
|
+
| `ignoreRegexPatternsDev` | `RegExp[]` | `[]` | Routes excluded from middleware in development only |
|
|
149
|
+
|
|
150
|
+
### `logging` options
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
| --- | --- | --- | --- |
|
|
154
|
+
| `logging.server.level` | `number` | `2` | Server log level (0=silent, 1=fatal, 2=error, 3=warn, 4=info/debug) |
|
|
155
|
+
| `logging.server.tag` | `string` | `'server'` | Server log tag |
|
|
156
|
+
| `logging.ssr.tag` | `string` | `'app-ssr'` | SSR log tag |
|
|
157
|
+
| `logging.client.tag` | `string` | `'app-client'` | Client log tag |
|
|
158
|
+
|
|
159
|
+
## Composables
|
|
160
|
+
|
|
161
|
+
All composables are auto-imported. No import statement needed.
|
|
162
|
+
|
|
163
|
+
### `useAuthContext(initiator?)`
|
|
164
|
+
|
|
165
|
+
Reactive authentication state and actions.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const { login, logout, authorize, isAuthenticated, user, session, processing, sanitizeError } = useAuthContext()
|
|
169
|
+
|
|
170
|
+
// login — redirects to the OIDC provider's authorization endpoint
|
|
171
|
+
await login('/dashboard')
|
|
172
|
+
|
|
173
|
+
// logout — revokes tokens and redirects to defaultLogoutRedirectUri
|
|
174
|
+
await logout()
|
|
175
|
+
|
|
176
|
+
// authorize — exchanges the authorization code for tokens (called on the callback page)
|
|
177
|
+
await authorize(new URLSearchParams(window.location.search))
|
|
178
|
+
|
|
179
|
+
// isAuthenticated — ComputedRef<boolean>, true when the user has a valid session
|
|
180
|
+
console.log(isAuthenticated.value) // true | false
|
|
181
|
+
|
|
182
|
+
// user — ComputedRef<AuthorizedUser>, the current authenticated user
|
|
183
|
+
console.log(user.value.email)
|
|
184
|
+
|
|
185
|
+
// processing — Ref<boolean>, true while an auth request is in flight
|
|
186
|
+
console.log(processing.value) // true during login/logout/authorize
|
|
187
|
+
|
|
188
|
+
// sanitizeError — normalizes any error to H3Error
|
|
189
|
+
const h3err = sanitizeError(unknownError)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `useSessionContext()`
|
|
193
|
+
|
|
194
|
+
Low-level session accessor. Prefer `useAuthContext` for most use cases.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const { getValue, refetch } = useSessionContext()
|
|
198
|
+
|
|
199
|
+
// getValue — returns the current NuxtSessionContext
|
|
200
|
+
const session = getValue()
|
|
201
|
+
|
|
202
|
+
// refetch — triggers a session re-fetch from the server
|
|
203
|
+
await refetch()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `useLogger(opts?)`
|
|
207
|
+
|
|
208
|
+
Create a tagged logger instance.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const logger = useLogger({ tag: 'my-feature', level: 4 })
|
|
212
|
+
|
|
213
|
+
logger.debug('Debug message')
|
|
214
|
+
logger.warn('Warning message')
|
|
215
|
+
logger.error('Error message', error)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Log levels: 0 silent, 1 fatal, 2 error (default), 3 warn, 4 info/debug
|
|
219
|
+
|
|
220
|
+
## Server Handlers
|
|
221
|
+
|
|
222
|
+
Server handlers are auto-imported into Nitro routes. No import needed.
|
|
223
|
+
|
|
224
|
+
### `NuxtAuthtsHandler(options?)`
|
|
225
|
+
|
|
226
|
+
Main auth handler. **Required** — create this file or auth endpoints return 404.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// server/api/_auth/[...].ts
|
|
230
|
+
export default NuxtAuthtsHandler({
|
|
231
|
+
userInfo: async (user, { openidUser }) => ({
|
|
232
|
+
...user,
|
|
233
|
+
id: openidUser?.sub ?? user.id,
|
|
234
|
+
email: openidUser?.email ?? user.email,
|
|
235
|
+
firstName: openidUser?.given_name ?? user.firstName,
|
|
236
|
+
lastName: openidUser?.family_name ?? user.lastName,
|
|
237
|
+
}),
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The `userInfo` callback maps OIDC claims to `AuthorizedUser`. It is called after the token exchange (when `fetchUserOnLogin` is true) and on every `GET_SESSION` request when `autoFetchUser` is true (results are cached for `autoFetchUserTtl` seconds).
|
|
242
|
+
|
|
243
|
+
### `NuxtForwardRequestHandler(opts)`
|
|
244
|
+
|
|
245
|
+
Authenticated reverse proxy — forwards requests to a backend service with the session access token attached.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// server/api/backend/[...].ts
|
|
249
|
+
export default NuxtForwardRequestHandler({
|
|
250
|
+
targetBaseUrl: process.env.BACKEND_API_URL,
|
|
251
|
+
proxyPrefix: '/api/backend',
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Route Protection
|
|
256
|
+
|
|
257
|
+
The module registers a global Nuxt middleware that runs on every navigation. Control access per page with `definePageMeta`.
|
|
258
|
+
|
|
259
|
+
### Require authentication
|
|
260
|
+
|
|
261
|
+
```vue
|
|
262
|
+
<!-- pages/dashboard.vue -->
|
|
263
|
+
<script setup lang="ts">
|
|
264
|
+
definePageMeta({
|
|
265
|
+
authts: { required: true },
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const { user, isAuthenticated, logout } = useAuthContext()
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<template>
|
|
272
|
+
<div>
|
|
273
|
+
<p>Welcome, {{ user.firstName }}</p>
|
|
274
|
+
<button @click="logout()">Sign out</button>
|
|
275
|
+
</div>
|
|
276
|
+
</template>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Unauthenticated users are redirected to `auth.loginUri` (default `/login`). The intended URL is preserved as the post-login redirect.
|
|
280
|
+
|
|
281
|
+
### Restrict to unauthenticated users (login page)
|
|
282
|
+
|
|
283
|
+
```vue
|
|
284
|
+
<!-- pages/login.vue -->
|
|
285
|
+
<script setup lang="ts">
|
|
286
|
+
definePageMeta({
|
|
287
|
+
authts: { unauthenticatedOnly: true },
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
const { login } = useAuthContext()
|
|
291
|
+
</script>
|
|
292
|
+
|
|
293
|
+
<template>
|
|
294
|
+
<button @click="login('/dashboard')">Sign in</button>
|
|
295
|
+
</template>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Authenticated users navigating to this page receive a 404 from middleware.
|
|
299
|
+
|
|
300
|
+
> **Warning:** Do not set both `required: true` and `unauthenticatedOnly: true` on the same page. This combination causes a middleware error.
|
|
301
|
+
|
|
302
|
+
> **Note:** `roles` is a reserved field in `AuthtsMiddlewareMeta` and is not currently enforced.
|
|
303
|
+
|
|
304
|
+
## Nuxt Hooks
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
// plugins/my-app.ts
|
|
308
|
+
export default defineNuxtPlugin((_nuxtApp) => {
|
|
309
|
+
// Fires after every successful authorization code exchange
|
|
310
|
+
_nuxtApp.hook('user:loggedIn', async () => {
|
|
311
|
+
console.log('User logged in — session is now populated')
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// Fires after successful logout
|
|
315
|
+
_nuxtApp.hook('user:loggedOut', async () => {
|
|
316
|
+
console.log('User logged out — session has been cleared')
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
// Trigger a session re-fetch manually (e.g. after a server-side state change)
|
|
320
|
+
// _nuxtApp.callHook('session:fetch', 'my-initiator')
|
|
321
|
+
|
|
322
|
+
// Fires after every session fetch — receives the latest session data
|
|
323
|
+
_nuxtApp.hook('session:changed', async (session) => {
|
|
324
|
+
console.log('Session updated:', session.isAuthenticated)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## TypeScript
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import type {
|
|
333
|
+
AuthorizedUser,
|
|
334
|
+
AuthtsMiddlewareMeta,
|
|
335
|
+
NuxtSessionContext,
|
|
336
|
+
SessionContext,
|
|
337
|
+
} from '@devcoffee/nuxt-core'
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Custom session data
|
|
341
|
+
|
|
342
|
+
`SessionContext` and `NuxtSessionContext` accept a generic `TData` parameter for type-safe custom session data.
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import type { SessionContext } from '@devcoffee/nuxt-core'
|
|
346
|
+
|
|
347
|
+
type MyAppData = {
|
|
348
|
+
preferences: { theme: string }
|
|
349
|
+
roles: string[]
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// In a server handler or Nitro plugin:
|
|
353
|
+
const session: SessionContext<MyAppData> = await getSession(sessionId, opts)
|
|
354
|
+
|
|
355
|
+
// Fully typed — no casting required
|
|
356
|
+
console.log(session.data.preferences.theme) // string
|
|
357
|
+
console.log(session.data.roles) // string[]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Security
|
|
361
|
+
|
|
362
|
+
### Production checklist
|
|
363
|
+
|
|
364
|
+
**1. Set `sessions.secret` — most important step.**
|
|
365
|
+
|
|
366
|
+
Without it, session ID cookies are unsigned and tokenSet storage is unencrypted. An attacker who gains read access to Nitro storage can extract access tokens.
|
|
367
|
+
|
|
368
|
+
Set a 32+ character random string:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
SESSION_SECRET=your-32-char-minimum-random-secret
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
When `sessions.secret` is configured:
|
|
375
|
+
- **HMAC-SHA256** signs the session ID cookie — tampering is detected with constant-time comparison
|
|
376
|
+
- **AES-256-GCM** encrypts the tokenSet in Nitro storage — keys are derived via HKDF-SHA256 with domain separation
|
|
377
|
+
|
|
378
|
+
**2. PKCE is enabled by default** — `usePkce: true` with `S256`. Do not disable it.
|
|
379
|
+
|
|
380
|
+
**3. Open redirect protection** — `isSameOrigin()` validates all post-auth redirects. External URLs are rejected with HTTP 400.
|
|
381
|
+
|
|
382
|
+
**4. Session ID entropy** — `crypto.randomBytes(32)`, 256-bit. UUID v4 is not used.
|
|
383
|
+
|
|
384
|
+
**5. HttpOnly cookies** — `httpOnly: true` and `secure: true` in production by default.
|
|
385
|
+
|
|
386
|
+
## Changelog
|
|
387
|
+
|
|
388
|
+
See [CHANGELOG.md](/CHANGELOG.md) for the full release history.
|
|
389
|
+
|
|
390
|
+
## Contributing
|
|
391
|
+
|
|
392
|
+
See [GUIDELINE.md](/GUIDELINE.md) for contribution guidelines, local development setup, and release instructions.
|
|
393
|
+
|
|
394
|
+
<!-- Badges -->
|
|
395
|
+
[npm-version-src]: https://img.shields.io/npm/v/@devcoffee/nuxt-core/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
396
|
+
[npm-version-href]: https://npmjs.com/package/@devcoffee/nuxt-core
|
|
397
|
+
|
|
398
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@devcoffee/nuxt-core.svg?style=flat&colorA=020420&colorB=00DC82
|
|
399
|
+
[npm-downloads-href]: https://npm.chart.dev/@devcoffee/nuxt-core
|
|
400
|
+
|
|
401
|
+
[license-src]: https://img.shields.io/npm/l/@devcoffee/nuxt-core.svg?style=flat&colorA=020420&colorB=00DC82
|
|
402
|
+
[license-href]: https://npmjs.com/package/@devcoffee/nuxt-core
|
|
403
|
+
|
|
404
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
405
|
+
[nuxt-href]: https://nuxt.com
|