@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 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 };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@icecat-studio/nuxt-auth",
3
+ "configKey": "auth",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "0.1.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }