@icecat-studio/nuxt-auth 0.1.0
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/LICENSE +21 -0
- package/README.md +644 -0
- package/dist/module.d.mts +7 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +131 -0
- package/dist/runtime/composables/useAuth.d.ts +54 -0
- package/dist/runtime/composables/useAuth.js +242 -0
- package/dist/runtime/composables/useAuthFetch.d.ts +13 -0
- package/dist/runtime/composables/useAuthFetch.js +57 -0
- package/dist/runtime/composables/useTokenManager.d.ts +16 -0
- package/dist/runtime/composables/useTokenManager.js +103 -0
- package/dist/runtime/middleware/auth.d.ts +2 -0
- package/dist/runtime/middleware/auth.js +31 -0
- package/dist/runtime/plugins/00.session-init.d.ts +2 -0
- package/dist/runtime/plugins/00.session-init.js +131 -0
- package/dist/runtime/plugins/01.token-refresh.client.d.ts +2 -0
- package/dist/runtime/plugins/01.token-refresh.client.js +102 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/types.d.ts +201 -0
- package/dist/runtime/types.js +0 -0
- package/dist/runtime/utils/helpers.d.ts +33 -0
- package/dist/runtime/utils/helpers.js +37 -0
- package/dist/runtime/utils/redirect.d.ts +7 -0
- package/dist/runtime/utils/redirect.js +6 -0
- package/dist/types.d.mts +3 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Icecat Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
# @icecat-studio/nuxt-auth
|
|
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
|
+
Modern authentication module for Nuxt 3+ with token-based auth, auto-refresh, SSR support, and smart tab coordination.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- π **Token-based Authentication** β JWT/Bearer token support out of the box
|
|
13
|
+
- π **Auto-refresh Tokens** β Automatic token renewal with configurable intervals
|
|
14
|
+
- π₯οΈ **SSR Ready** β Full server-side rendering support with proper hydration
|
|
15
|
+
- π― **Smart Tab Coordination** β Prevents multiple tabs from refreshing simultaneously
|
|
16
|
+
- βΈοΈ **Pause on Inactive** β Automatically pauses refresh when tab is hidden
|
|
17
|
+
- π‘οΈ **Auth Fetch** β Composable with automatic auth headers and 401 retry
|
|
18
|
+
- π¨ **Flexible Token Management** β Client-managed, server-managed (httpOnly), or no refresh
|
|
19
|
+
- π¦ **Type-safe** β Full TypeScript support with user type augmentation
|
|
20
|
+
- π **Composable API** β Simple `useAuth()` and `useAuthFetch()` composables
|
|
21
|
+
- π§ **Route Middleware** β Built-in auth middleware for route protection
|
|
22
|
+
|
|
23
|
+
## Quick Setup
|
|
24
|
+
|
|
25
|
+
Install the module:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @icecat-studio/nuxt-auth
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Add it to `nuxt.config.ts`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export default defineNuxtConfig({
|
|
35
|
+
modules: ['@icecat-studio/nuxt-auth'],
|
|
36
|
+
|
|
37
|
+
auth: {
|
|
38
|
+
baseUrl: '/api/auth',
|
|
39
|
+
endpoints: {
|
|
40
|
+
login: { path: '/login', method: 'post' },
|
|
41
|
+
logout: { path: '/logout', method: 'post' },
|
|
42
|
+
refresh: { path: '/refresh', method: 'post' },
|
|
43
|
+
user: { path: '/user', method: 'get' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it! You can now use `@icecat-studio/nuxt-auth` in your Nuxt app β¨
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Login
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
const auth = useAuth()
|
|
58
|
+
|
|
59
|
+
const login = async () => {
|
|
60
|
+
try {
|
|
61
|
+
await auth.login({
|
|
62
|
+
email: 'user@example.com',
|
|
63
|
+
password: 'password',
|
|
64
|
+
})
|
|
65
|
+
// Redirects to home page automatically
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Login failed:', error)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
You can control redirect behavior per call:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// Disable redirect
|
|
78
|
+
await auth.login(credentials, { redirect: false })
|
|
79
|
+
|
|
80
|
+
// Custom redirect path
|
|
81
|
+
await auth.login(credentials, { redirect: '/dashboard' })
|
|
82
|
+
|
|
83
|
+
// External redirect
|
|
84
|
+
await auth.login(credentials, { redirect: { url: 'https://app.example.com', external: true } })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Registration
|
|
88
|
+
|
|
89
|
+
```vue
|
|
90
|
+
<script setup lang="ts">
|
|
91
|
+
const auth = useAuth()
|
|
92
|
+
|
|
93
|
+
const register = async () => {
|
|
94
|
+
try {
|
|
95
|
+
await auth.register({
|
|
96
|
+
name: 'John Doe',
|
|
97
|
+
email: 'john@example.com',
|
|
98
|
+
password: 'password',
|
|
99
|
+
})
|
|
100
|
+
// If the API returns tokens β logs in automatically
|
|
101
|
+
// Otherwise β redirects to login page
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Registration failed:', error)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Protect Routes
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
definePageMeta({
|
|
115
|
+
auth: true, // Requires authentication
|
|
116
|
+
})
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For guest-only pages (login, register):
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<script setup lang="ts">
|
|
124
|
+
definePageMeta({
|
|
125
|
+
auth: 'guest', // Redirects authenticated users away
|
|
126
|
+
})
|
|
127
|
+
</script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Public pages (opt out of global middleware):
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<script setup lang="ts">
|
|
134
|
+
definePageMeta({
|
|
135
|
+
auth: false, // Accessible to everyone
|
|
136
|
+
})
|
|
137
|
+
</script>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Access User Data
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
<script setup lang="ts">
|
|
144
|
+
const auth = useAuth()
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<template>
|
|
148
|
+
<div v-if="auth.loggedIn.value">
|
|
149
|
+
<p>Welcome, {{ auth.user.value?.name }}!</p>
|
|
150
|
+
<button @click="auth.logout()">Logout</button>
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Authenticated HTTP Requests
|
|
156
|
+
|
|
157
|
+
Use `useAuthFetch()` for requests that need authentication. It automatically injects auth headers and handles 401 responses:
|
|
158
|
+
|
|
159
|
+
```vue
|
|
160
|
+
<script setup lang="ts">
|
|
161
|
+
const authFetch = useAuthFetch()
|
|
162
|
+
|
|
163
|
+
const { data } = await useAsyncData(() => authFetch('/api/orders'))
|
|
164
|
+
</script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`useAuthFetch` provides:
|
|
168
|
+
- Automatic `Authorization` header injection
|
|
169
|
+
- Proactive token refresh if the access token is missing but refresh is available
|
|
170
|
+
- Automatic retry on 401 after a successful token refresh (client-side)
|
|
171
|
+
- Cookie forwarding during SSR for server-managed tokens
|
|
172
|
+
|
|
173
|
+
## Authentication Modes
|
|
174
|
+
|
|
175
|
+
The module supports three token management strategies:
|
|
176
|
+
|
|
177
|
+
### Client-Managed Refresh Token
|
|
178
|
+
|
|
179
|
+
The refresh token is stored in a client-accessible cookie and sent in the request body during refresh.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
export default defineNuxtConfig({
|
|
183
|
+
auth: {
|
|
184
|
+
refreshToken: {
|
|
185
|
+
serverManaged: false, // default
|
|
186
|
+
property: 'refreshToken',
|
|
187
|
+
bodyProperty: 'refreshToken',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Server-Managed Refresh Token (httpOnly)
|
|
194
|
+
|
|
195
|
+
The refresh token is managed entirely by the server via an httpOnly cookie. It is never accessible to JavaScript β the browser sends it automatically.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
export default defineNuxtConfig({
|
|
199
|
+
auth: {
|
|
200
|
+
refreshToken: {
|
|
201
|
+
serverManaged: true,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### No Refresh (Access Token Only)
|
|
208
|
+
|
|
209
|
+
Disable the refresh endpoint for simple access-token-only flows. The session ends when the token expires.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
export default defineNuxtConfig({
|
|
213
|
+
auth: {
|
|
214
|
+
endpoints: {
|
|
215
|
+
refresh: false,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Configuration
|
|
222
|
+
|
|
223
|
+
### Endpoints
|
|
224
|
+
|
|
225
|
+
Each endpoint can be configured or disabled individually:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
export default defineNuxtConfig({
|
|
229
|
+
auth: {
|
|
230
|
+
baseUrl: '/api/auth',
|
|
231
|
+
endpoints: {
|
|
232
|
+
login: { path: '/login', method: 'post' },
|
|
233
|
+
logout: { path: '/logout', method: 'post' }, // set to `false` to disable
|
|
234
|
+
register: { path: '/register', method: 'post' }, // set to `false` to disable
|
|
235
|
+
refresh: { path: '/refresh', method: 'post' }, // set to `false` to disable
|
|
236
|
+
user: { path: '/user', method: 'get' },
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
You can also pass additional fetch options per endpoint:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
endpoints: {
|
|
246
|
+
login: {
|
|
247
|
+
path: '/login',
|
|
248
|
+
method: 'post',
|
|
249
|
+
fetchOptions: { credentials: 'include' },
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Access Token
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
export default defineNuxtConfig({
|
|
258
|
+
auth: {
|
|
259
|
+
accessToken: {
|
|
260
|
+
property: 'accessToken', // Property in API response
|
|
261
|
+
cookieName: 'auth.access_token',
|
|
262
|
+
type: 'Bearer', // Token type prefix
|
|
263
|
+
headerName: 'Authorization', // Header name
|
|
264
|
+
maxAge: 900, // 15 minutes (in seconds)
|
|
265
|
+
httpOnly: false,
|
|
266
|
+
secure: true,
|
|
267
|
+
sameSite: 'lax',
|
|
268
|
+
path: '/',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Refresh Token
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
export default defineNuxtConfig({
|
|
278
|
+
auth: {
|
|
279
|
+
refreshToken: {
|
|
280
|
+
property: 'refreshToken', // Property in API response
|
|
281
|
+
cookieName: 'auth.refresh_token',
|
|
282
|
+
serverManaged: false, // true = httpOnly cookie managed by server
|
|
283
|
+
bodyProperty: 'refreshToken', // Key name in refresh request body
|
|
284
|
+
maxAge: 604800, // 7 days (in seconds)
|
|
285
|
+
httpOnly: false,
|
|
286
|
+
secure: true,
|
|
287
|
+
sameSite: 'lax',
|
|
288
|
+
path: '/',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Auto-Refresh
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
export default defineNuxtConfig({
|
|
298
|
+
auth: {
|
|
299
|
+
autoRefresh: {
|
|
300
|
+
enabled: true,
|
|
301
|
+
interval: 840, // 14 minutes (in seconds)
|
|
302
|
+
pauseOnInactive: true, // Pause when tab is hidden
|
|
303
|
+
enableTabCoordination: true, // Coordinate between tabs
|
|
304
|
+
coordinationCookieName: 'auth.last_refresh',
|
|
305
|
+
coordinationThreshold: 5, // 5 seconds β skip if another tab refreshed within this time
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Redirects
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
export default defineNuxtConfig({
|
|
315
|
+
auth: {
|
|
316
|
+
redirect: {
|
|
317
|
+
login: '/login', // Where to redirect unauthenticated users
|
|
318
|
+
logout: '/', // Where to redirect after logout
|
|
319
|
+
home: '/', // Where to redirect after login
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Redirects also support external URLs:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
redirect: {
|
|
329
|
+
home: { url: 'https://app.example.com/dashboard', external: true },
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### User
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
export default defineNuxtConfig({
|
|
337
|
+
auth: {
|
|
338
|
+
user: {
|
|
339
|
+
property: undefined, // Nested property path (e.g., 'data.user'), undefined = entire response
|
|
340
|
+
autoFetch: true, // Automatically fetch user after login/refresh
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Global Middleware
|
|
347
|
+
|
|
348
|
+
Enable authentication check on all routes by default:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
export default defineNuxtConfig({
|
|
352
|
+
auth: {
|
|
353
|
+
globalMiddleware: true,
|
|
354
|
+
},
|
|
355
|
+
})
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Then opt out specific routes with `auth: false` in `definePageMeta`.
|
|
359
|
+
|
|
360
|
+
## API Reference
|
|
361
|
+
|
|
362
|
+
### `useAuth<T>()`
|
|
363
|
+
|
|
364
|
+
The main composable for authentication. Returns a singleton instance.
|
|
365
|
+
|
|
366
|
+
#### State
|
|
367
|
+
|
|
368
|
+
| Property | Type | Description |
|
|
369
|
+
|---|---|---|
|
|
370
|
+
| `user` | `Ref<T \| null>` | Current user data (reactive) |
|
|
371
|
+
| `status` | `Ref<AuthStatus>` | Auth status (see below) |
|
|
372
|
+
| `loggedIn` | `ComputedRef<boolean>` | Whether the user is logged in |
|
|
373
|
+
| `accessToken` | `Ref<string \| null>` | Current access token |
|
|
374
|
+
| `refreshToken` | `Ref<string \| null>` | Current refresh token (`null` if server-managed) |
|
|
375
|
+
| `canRefresh` | `ComputedRef<boolean>` | Whether token refresh is available |
|
|
376
|
+
|
|
377
|
+
**Auth status values:**
|
|
378
|
+
|
|
379
|
+
| Status | Description |
|
|
380
|
+
|---|---|
|
|
381
|
+
| `idle` | Initial state, no auth activity |
|
|
382
|
+
| `loading` | Login or registration in progress |
|
|
383
|
+
| `refreshing` | Token refresh in progress |
|
|
384
|
+
| `authenticated` | User is authenticated |
|
|
385
|
+
| `unauthenticated` | No valid session |
|
|
386
|
+
|
|
387
|
+
**`loggedIn` logic:**
|
|
388
|
+
- With refresh endpoint: `true` when status is `authenticated` or `refreshing` (session is alive until refresh explicitly fails)
|
|
389
|
+
- Without refresh endpoint: `true` when status is `authenticated` **and** access token exists
|
|
390
|
+
|
|
391
|
+
#### Methods
|
|
392
|
+
|
|
393
|
+
| Method | Signature | Description |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| `login` | `(credentials: Record<string, any>, options?: LoginOptions) => Promise<void>` | Login with credentials |
|
|
396
|
+
| `logout` | `() => Promise<void>` | Logout and clear session |
|
|
397
|
+
| `register` | `(data: Record<string, any>) => Promise<void>` | Register a new user |
|
|
398
|
+
| `refresh` | `() => Promise<void>` | Manually refresh tokens |
|
|
399
|
+
| `fetchUser` | `() => Promise<void>` | Fetch user data from the user endpoint |
|
|
400
|
+
| `getAuthHeaders` | `() => Record<string, string>` | Get authorization headers for manual requests |
|
|
401
|
+
|
|
402
|
+
### `useAuthFetch()`
|
|
403
|
+
|
|
404
|
+
Returns an enhanced `$fetch` function with automatic auth handling:
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
const authFetch = useAuthFetch()
|
|
408
|
+
|
|
409
|
+
// Typed response
|
|
410
|
+
const user = await authFetch<User>('/api/me')
|
|
411
|
+
|
|
412
|
+
// With options
|
|
413
|
+
const data = await authFetch('/api/data', { method: 'POST', body: { key: 'value' } })
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Behavior:**
|
|
417
|
+
1. Injects `Authorization` header with the current access token
|
|
418
|
+
2. If no access token but refresh is available β refreshes first, then makes the request
|
|
419
|
+
3. On 401 response (client-side only) β refreshes the token and retries the request once
|
|
420
|
+
4. During SSR β forwards incoming request cookies for server-managed tokens
|
|
421
|
+
|
|
422
|
+
## Advanced Usage
|
|
423
|
+
|
|
424
|
+
### Custom API Calls with Auth Headers
|
|
425
|
+
|
|
426
|
+
If you need auth headers without using `useAuthFetch`:
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
const auth = useAuth()
|
|
430
|
+
|
|
431
|
+
const data = await $fetch('/api/protected', {
|
|
432
|
+
headers: auth.getAuthHeaders(),
|
|
433
|
+
})
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Manual Token Refresh
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
const auth = useAuth()
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
await auth.refresh()
|
|
443
|
+
console.log('Token refreshed successfully')
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
console.error('Refresh failed:', error)
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Controlling the Refresh Manager
|
|
451
|
+
|
|
452
|
+
The auto-refresh scheduler is exposed via `useNuxtApp().$refreshManager`:
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
const { $refreshManager } = useNuxtApp()
|
|
456
|
+
|
|
457
|
+
$refreshManager?.stop() // Stop auto-refresh
|
|
458
|
+
$refreshManager?.start() // Start auto-refresh
|
|
459
|
+
$refreshManager?.refresh() // Trigger immediate refresh
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### TypeScript: Augmenting the User Type
|
|
463
|
+
|
|
464
|
+
Define your user interface globally:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
// types/auth.d.ts
|
|
468
|
+
declare module '@icecat-studio/nuxt-auth' {
|
|
469
|
+
interface User {
|
|
470
|
+
id: number
|
|
471
|
+
email: string
|
|
472
|
+
name: string
|
|
473
|
+
avatar?: string
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Then `useAuth()` will infer the correct type:
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
const auth = useAuth()
|
|
482
|
+
auth.user.value?.name // string
|
|
483
|
+
auth.user.value?.id // number
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Or pass the type parameter directly:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
interface MyUser {
|
|
490
|
+
id: number
|
|
491
|
+
name: string
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const auth = useAuth<MyUser>()
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## How It Works
|
|
498
|
+
|
|
499
|
+
### Session Initialization
|
|
500
|
+
|
|
501
|
+
1. On app start, checks for existing tokens in cookies
|
|
502
|
+
2. **SSR:** Attempts token refresh on the server; stores the result for the client
|
|
503
|
+
3. **Client hydration:** If the server already refreshed, skips duplicate refresh and fetches user data
|
|
504
|
+
4. **Client-only navigation:** Refreshes tokens and restores session normally
|
|
505
|
+
|
|
506
|
+
### Auto-Refresh
|
|
507
|
+
|
|
508
|
+
1. Schedules token refresh at the configured interval (default: 14 minutes)
|
|
509
|
+
2. Pauses when the tab is hidden (if `pauseOnInactive` enabled)
|
|
510
|
+
3. Resumes with an immediate refresh when the tab becomes visible
|
|
511
|
+
4. On network error β reschedules with a doubled interval
|
|
512
|
+
5. On server rejection (4xx) β stops auto-refresh and clears session
|
|
513
|
+
|
|
514
|
+
### Tab Coordination
|
|
515
|
+
|
|
516
|
+
1. Uses a shared cookie to track the last refresh timestamp
|
|
517
|
+
2. Before refreshing, checks if another tab refreshed recently (within `coordinationThreshold`)
|
|
518
|
+
3. Skips refresh if within the threshold, reschedules normal interval
|
|
519
|
+
4. Updates the timestamp after a successful refresh
|
|
520
|
+
|
|
521
|
+
### Error Handling
|
|
522
|
+
|
|
523
|
+
| Scenario | Behavior |
|
|
524
|
+
|---|---|
|
|
525
|
+
| **Server rejection (4xx)** | Clears tokens, sets status to `unauthenticated`, stops auto-refresh |
|
|
526
|
+
| **Network error** | Keeps tokens intact, reverts status, reschedules with doubled interval |
|
|
527
|
+
| **401 in `useAuthFetch`** | Refreshes token and retries request once (client-only) |
|
|
528
|
+
| **Middleware: protected page, no session** | Attempts refresh; redirects to login on failure |
|
|
529
|
+
|
|
530
|
+
## Default Configuration
|
|
531
|
+
|
|
532
|
+
Full configuration with all default values:
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
export default defineNuxtConfig({
|
|
536
|
+
auth: {
|
|
537
|
+
baseUrl: '/api/auth',
|
|
538
|
+
endpoints: {
|
|
539
|
+
login: { path: '/login', method: 'post' },
|
|
540
|
+
logout: { path: '/logout', method: 'post' },
|
|
541
|
+
register: { path: '/register', method: 'post' },
|
|
542
|
+
refresh: { path: '/refresh', method: 'post' },
|
|
543
|
+
user: { path: '/user', method: 'get' },
|
|
544
|
+
},
|
|
545
|
+
accessToken: {
|
|
546
|
+
property: 'accessToken',
|
|
547
|
+
cookieName: 'auth.access_token',
|
|
548
|
+
httpOnly: false,
|
|
549
|
+
secure: true,
|
|
550
|
+
sameSite: 'lax',
|
|
551
|
+
path: '/',
|
|
552
|
+
maxAge: 900, // 15 minutes
|
|
553
|
+
type: 'Bearer',
|
|
554
|
+
headerName: 'Authorization',
|
|
555
|
+
},
|
|
556
|
+
refreshToken: {
|
|
557
|
+
property: 'refreshToken',
|
|
558
|
+
cookieName: 'auth.refresh_token',
|
|
559
|
+
httpOnly: false,
|
|
560
|
+
secure: true,
|
|
561
|
+
sameSite: 'lax',
|
|
562
|
+
path: '/',
|
|
563
|
+
maxAge: 604800, // 7 days
|
|
564
|
+
serverManaged: false,
|
|
565
|
+
bodyProperty: 'refreshToken',
|
|
566
|
+
},
|
|
567
|
+
autoRefresh: {
|
|
568
|
+
enabled: true,
|
|
569
|
+
interval: 840, // 14 minutes
|
|
570
|
+
pauseOnInactive: true,
|
|
571
|
+
enableTabCoordination: true,
|
|
572
|
+
coordinationCookieName: 'auth.last_refresh',
|
|
573
|
+
coordinationThreshold: 5, // 5 seconds
|
|
574
|
+
},
|
|
575
|
+
user: {
|
|
576
|
+
property: undefined,
|
|
577
|
+
autoFetch: true,
|
|
578
|
+
},
|
|
579
|
+
redirect: {
|
|
580
|
+
login: '/login',
|
|
581
|
+
logout: '/',
|
|
582
|
+
home: '/',
|
|
583
|
+
},
|
|
584
|
+
globalMiddleware: false,
|
|
585
|
+
},
|
|
586
|
+
})
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## Contribution
|
|
590
|
+
|
|
591
|
+
<details>
|
|
592
|
+
<summary>Local development</summary>
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
# Install dependencies
|
|
596
|
+
npm install
|
|
597
|
+
|
|
598
|
+
# Generate type stubs
|
|
599
|
+
npm run dev:prepare
|
|
600
|
+
|
|
601
|
+
# Develop with client-managed mode
|
|
602
|
+
npm run dev:client
|
|
603
|
+
|
|
604
|
+
# Develop with server-managed mode
|
|
605
|
+
npm run dev:server
|
|
606
|
+
|
|
607
|
+
# Develop without token refresh
|
|
608
|
+
npm run dev:no-refresh
|
|
609
|
+
|
|
610
|
+
# Build the playground
|
|
611
|
+
npm run dev:build
|
|
612
|
+
|
|
613
|
+
# Run ESLint
|
|
614
|
+
npm run lint
|
|
615
|
+
|
|
616
|
+
# Run Vitest
|
|
617
|
+
npm run test
|
|
618
|
+
npm run test:watch
|
|
619
|
+
|
|
620
|
+
# Run type checking
|
|
621
|
+
npm run test:types
|
|
622
|
+
|
|
623
|
+
# Release new version
|
|
624
|
+
npm run release
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
</details>
|
|
628
|
+
|
|
629
|
+
## License
|
|
630
|
+
|
|
631
|
+
[MIT License](./LICENSE)
|
|
632
|
+
|
|
633
|
+
<!-- Badges -->
|
|
634
|
+
[npm-version-src]: https://img.shields.io/npm/v/@icecat-studio/nuxt-auth/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
635
|
+
[npm-version-href]: https://npmjs.com/package/@icecat-studio/nuxt-auth
|
|
636
|
+
|
|
637
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@icecat-studio/nuxt-auth.svg?style=flat&colorA=020420&colorB=00DC82
|
|
638
|
+
[npm-downloads-href]: https://npm.chart.dev/@icecat-studio/nuxt-auth
|
|
639
|
+
|
|
640
|
+
[license-src]: https://img.shields.io/npm/l/@icecat-studio/nuxt-auth.svg?style=flat&colorA=020420&colorB=00DC82
|
|
641
|
+
[license-href]: https://npmjs.com/package/@icecat-studio/nuxt-auth
|
|
642
|
+
|
|
643
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
644
|
+
[nuxt-href]: https://nuxt.com
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { ModuleOptions } from '../dist/runtime/types.js';
|
|
3
|
+
export { ModuleOptions } from '../dist/runtime/types.js';
|
|
4
|
+
|
|
5
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
|
+
|
|
7
|
+
export { _default as default };
|