@douvery/auth 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/README.md +440 -89
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,88 +1,114 @@
1
1
  # @douvery/auth
2
2
 
3
- OAuth 2.0/OIDC client library for Douvery authentication.
3
+ <p align="center">
4
+ <img src="https://img.shields.io/npm/v/@douvery/auth?style=flat-square&color=blue" alt="npm version" />
5
+ <img src="https://img.shields.io/npm/dm/@douvery/auth?style=flat-square&color=green" alt="downloads" />
6
+ <img src="https://img.shields.io/npm/l/@douvery/auth?style=flat-square" alt="license" />
7
+ <img src="https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square&logo=typescript" alt="typescript" />
8
+ </p>
4
9
 
5
- ## Packages
10
+ <p align="center">
11
+ <strong>🔐 OAuth 2.0/OIDC client library for Douvery authentication</strong>
12
+ </p>
6
13
 
7
- | Package | Description |
8
- |---------|-------------|
9
- | [@douvery/auth](./packages/core) | Core client library (vanilla TypeScript) |
10
- | [@douvery/auth-react](./packages/react) | React hooks and components |
11
- | [@douvery/auth-qwik](./packages/qwik) | Qwik hooks and components |
14
+ <p align="center">
15
+ Secure, type-safe authentication with PKCE support for React, Qwik, and vanilla TypeScript.
16
+ </p>
12
17
 
13
- ## Installation
18
+ ---
14
19
 
15
- ### React
20
+ ## âœĻ Features
16
21
 
17
- ```bash
18
- npm install @douvery/auth-react
19
- # or
20
- pnpm add @douvery/auth-react
21
- ```
22
+ - 🔒 **PKCE Support** - Secure authorization code flow with Proof Key for Code Exchange
23
+ - 🔄 **Auto Token Refresh** - Automatic token refresh before expiry
24
+ - ðŸ’ū **Multiple Storage Options** - localStorage, sessionStorage, memory, or cookies
25
+ - ðŸ“Ķ **Tree Shakeable** - Import only what you need
26
+ - ðŸŽŊ **TypeScript First** - Full TypeScript support with comprehensive types
27
+ - ⚛ïļ **React Adapter** - Provider and hooks for React 18+
28
+ - ⚡ **Qwik Adapter** - Signal-based reactivity for Qwik
29
+ - ðŸ“Ą **Event System** - Subscribe to auth events (login, logout, token refresh)
30
+ - 🌐 **SSR Compatible** - Works with server-side rendering
31
+ - ðŸŠķ **Lightweight** - ~23KB core, framework adapters add minimal overhead
22
32
 
23
- ### Qwik
33
+ ---
24
34
 
25
- ```bash
26
- npm install @douvery/auth-qwik
27
- # or
28
- pnpm add @douvery/auth-qwik
29
- ```
30
-
31
- ### Vanilla TypeScript
35
+ ## ðŸ“Ķ Installation
32
36
 
33
37
  ```bash
38
+ # npm
34
39
  npm install @douvery/auth
35
- # or
40
+
41
+ # pnpm
36
42
  pnpm add @douvery/auth
43
+
44
+ # bun
45
+ bun add @douvery/auth
46
+
47
+ # yarn
48
+ yarn add @douvery/auth
37
49
  ```
38
50
 
39
- ## Quick Start
51
+ ### Peer Dependencies (Optional)
52
+
53
+ - **React**: `react >= 18.0.0` (only if using `@douvery/auth/react`)
54
+ - **Qwik**: `@builder.io/qwik >= 1.0.0` (only if using `@douvery/auth/qwik`)
55
+
56
+ ---
57
+
58
+ ## 🚀 Quick Start
40
59
 
41
60
  ### React
42
61
 
43
62
  ```tsx
44
- import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth-react';
63
+ import { DouveryAuthProvider, useDouveryAuth, useUser } from '@douvery/auth/react';
45
64
 
46
- // Wrap your app with the provider
65
+ // 1. Wrap your app with the provider
47
66
  function App() {
48
67
  return (
49
68
  <DouveryAuthProvider
50
69
  config={{
51
70
  clientId: 'your-client-id',
52
- redirectUri: 'http://localhost:3000/callback',
71
+ redirectUri: window.location.origin + '/callback',
53
72
  issuer: 'https://auth.douvery.com',
54
73
  scopes: ['openid', 'profile', 'email'],
55
74
  }}
75
+ onAuthenticated={(user) => console.log('Logged in:', user)}
76
+ onLogout={() => console.log('Logged out')}
77
+ onError={(error) => console.error('Auth error:', error)}
56
78
  >
57
79
  <YourApp />
58
80
  </DouveryAuthProvider>
59
81
  );
60
82
  }
61
83
 
62
- // Use the hook in your components
84
+ // 2. Use hooks in your components
63
85
  function LoginButton() {
64
- const { login, logout, isAuthenticated, user } = useDouveryAuth();
86
+ const { login, logout, isAuthenticated, isLoading } = useDouveryAuth();
87
+ const user = useUser();
88
+
89
+ if (isLoading) return <span>Loading...</span>;
65
90
 
66
91
  if (isAuthenticated) {
67
92
  return (
68
93
  <div>
94
+ <img src={user?.picture} alt={user?.name} />
69
95
  <p>Welcome, {user?.name}!</p>
70
96
  <button onClick={() => logout()}>Logout</button>
71
97
  </div>
72
98
  );
73
99
  }
74
100
 
75
- return <button onClick={() => login()}>Login</button>;
101
+ return <button onClick={() => login()}>Login with Douvery</button>;
76
102
  }
77
103
  ```
78
104
 
79
105
  ### Qwik
80
106
 
81
107
  ```tsx
82
- import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth-qwik';
83
- import { component$ } from '@builder.io/qwik';
108
+ import { DouveryAuthProvider, useDouveryAuth, useUser, useAuthActions } from '@douvery/auth/qwik';
109
+ import { component$, Slot } from '@builder.io/qwik';
84
110
 
85
- // Wrap your app with the provider
111
+ // 1. Wrap your app with the provider
86
112
  export default component$(() => {
87
113
  return (
88
114
  <DouveryAuthProvider
@@ -98,134 +124,459 @@ export default component$(() => {
98
124
  );
99
125
  });
100
126
 
101
- // Use the hook in your components
127
+ // 2. Use hooks in your components (signal-based)
102
128
  export const LoginButton = component$(() => {
103
- const { login, logout, isAuthenticated, user } = useDouveryAuth();
129
+ const user = useUser();
130
+ const { login, logout, isLoading } = useAuthActions();
104
131
 
105
132
  return (
106
133
  <>
107
- {isAuthenticated.value ? (
134
+ {user.value ? (
108
135
  <div>
109
- <p>Welcome, {user.value?.name}!</p>
136
+ <img src={user.value.picture} alt={user.value.name} />
137
+ <p>Welcome, {user.value.name}!</p>
110
138
  <button onClick$={() => logout()}>Logout</button>
111
139
  </div>
112
140
  ) : (
113
- <button onClick$={() => login()}>Login</button>
141
+ <button onClick$={() => login()} disabled={isLoading.value}>
142
+ {isLoading.value ? 'Loading...' : 'Login with Douvery'}
143
+ </button>
114
144
  )}
115
145
  </>
116
146
  );
117
147
  });
118
148
  ```
119
149
 
120
- ### Vanilla TypeScript
150
+ ### Vanilla TypeScript / JavaScript
121
151
 
122
152
  ```typescript
123
153
  import { createDouveryAuth } from '@douvery/auth';
124
154
 
155
+ // 1. Create the auth client
125
156
  const auth = createDouveryAuth({
126
157
  clientId: 'your-client-id',
127
- redirectUri: 'http://localhost:3000/callback',
158
+ redirectUri: window.location.origin + '/callback',
128
159
  issuer: 'https://auth.douvery.com',
129
160
  scopes: ['openid', 'profile', 'email'],
130
161
  });
131
162
 
132
- // Initialize (checks for existing session or handles callback)
163
+ // 2. Subscribe to auth events
164
+ auth.subscribe((event) => {
165
+ switch (event.type) {
166
+ case 'LOGIN_SUCCESS':
167
+ console.log('Logged in:', event.user);
168
+ break;
169
+ case 'LOGOUT_SUCCESS':
170
+ console.log('Logged out');
171
+ break;
172
+ case 'TOKEN_REFRESHED':
173
+ console.log('Token refreshed');
174
+ break;
175
+ case 'SESSION_EXPIRED':
176
+ console.log('Session expired');
177
+ break;
178
+ }
179
+ });
180
+
181
+ // 3. Initialize (handles callback if present, restores session)
133
182
  await auth.initialize();
134
183
 
135
- // Login
136
- await auth.login();
184
+ // 4. Check authentication state
185
+ const state = auth.getState();
186
+ console.log('Status:', state.status); // 'loading' | 'authenticated' | 'unauthenticated'
187
+ console.log('User:', state.user);
137
188
 
138
- // After callback, get user
139
- const user = auth.getUser();
140
- console.log(user);
189
+ // 5. Login (redirects to auth server)
190
+ await auth.login({ returnTo: '/dashboard' });
141
191
 
142
- // Get access token (auto-refreshes if needed)
192
+ // 6. Get access token (auto-refreshes if needed)
143
193
  const token = await auth.getAccessToken();
194
+ fetch('/api/protected', {
195
+ headers: { Authorization: `Bearer ${token}` }
196
+ });
144
197
 
145
- // Logout
198
+ // 7. Logout
146
199
  await auth.logout();
147
200
  ```
148
201
 
149
- ## Configuration Options
202
+ ---
203
+
204
+ ## 📖 API Reference
205
+
206
+ ### Configuration
150
207
 
151
208
  ```typescript
152
209
  interface DouveryAuthConfig {
153
- /** OAuth Client ID */
210
+ /** OAuth Client ID (required) */
154
211
  clientId: string;
155
212
 
156
- /** Authorization server base URL (default: "https://auth.douvery.com") */
157
- issuer?: string;
158
-
159
- /** Redirect URI after authentication */
213
+ /** Redirect URI after authentication (required) */
160
214
  redirectUri: string;
161
215
 
216
+ /** Authorization server base URL */
217
+ issuer?: string; // default: "https://auth.douvery.com"
218
+
162
219
  /** Post-logout redirect URI */
163
220
  postLogoutRedirectUri?: string;
164
221
 
165
- /** OAuth scopes to request (default: ["openid", "profile", "email"]) */
166
- scopes?: string[];
222
+ /** OAuth scopes to request */
223
+ scopes?: string[]; // default: ["openid", "profile", "email"]
167
224
 
168
- /** Token storage strategy (default: "localStorage") */
169
- storage?: "localStorage" | "sessionStorage" | "memory" | "cookie";
225
+ /** Token storage strategy */
226
+ storage?: "localStorage" | "sessionStorage" | "memory" | "cookie"; // default: "localStorage"
170
227
 
171
- /** Auto-refresh tokens before expiry (default: true) */
172
- autoRefresh?: boolean;
228
+ /** Custom storage implementation */
229
+ customStorage?: TokenStorage;
173
230
 
174
- /** Seconds before expiry to trigger refresh (default: 60) */
175
- refreshThreshold?: number;
231
+ /** Auto-refresh tokens before expiry */
232
+ autoRefresh?: boolean; // default: true
176
233
 
177
- /** Enable debug logging (default: false) */
178
- debug?: boolean;
234
+ /** Seconds before expiry to trigger refresh */
235
+ refreshThreshold?: number; // default: 60
236
+
237
+ /** Enable debug logging */
238
+ debug?: boolean; // default: false
179
239
  }
180
240
  ```
181
241
 
182
- ## Login Options
242
+ ### Login Options
183
243
 
184
244
  ```typescript
185
245
  await auth.login({
186
246
  // URL to return to after login
187
247
  returnTo: '/dashboard',
188
248
 
189
- // Force re-authentication
190
- prompt: 'login',
249
+ // Force re-authentication or consent
250
+ prompt: 'login' | 'consent' | 'select_account' | 'none',
191
251
 
192
- // Pre-fill email
252
+ // Pre-fill email/username
193
253
  loginHint: 'user@example.com',
194
254
 
195
- // UI locale
255
+ // UI locale preference
196
256
  uiLocales: 'es',
257
+
258
+ // Maximum authentication age in seconds
259
+ maxAge: 3600,
260
+
261
+ // Additional authorization parameters
262
+ authorizationParams: {
263
+ audience: 'https://api.example.com',
264
+ },
197
265
  });
198
266
  ```
199
267
 
200
- ## Features
268
+ ### Logout Options
269
+
270
+ ```typescript
271
+ await auth.logout({
272
+ // URL to return to after logout
273
+ returnTo: 'https://example.com',
274
+
275
+ // End session at IdP (federated logout)
276
+ federated: true, // default: true
277
+
278
+ // Only clear local session, don't redirect
279
+ localOnly: false, // default: false
280
+ });
281
+ ```
282
+
283
+ ### Auth State
284
+
285
+ ```typescript
286
+ interface AuthState {
287
+ status: 'loading' | 'authenticated' | 'unauthenticated';
288
+ user: User | null;
289
+ tokens: TokenInfo | null;
290
+ error: AuthError | null;
291
+ }
292
+
293
+ interface User {
294
+ id: string;
295
+ email?: string;
296
+ emailVerified?: boolean;
297
+ name?: string;
298
+ firstName?: string;
299
+ lastName?: string;
300
+ picture?: string;
301
+ phoneNumber?: string;
302
+ locale?: string;
303
+ [key: string]: unknown;
304
+ }
305
+ ```
306
+
307
+ ### Auth Events
308
+
309
+ ```typescript
310
+ type AuthEvent =
311
+ | { type: 'INITIALIZED' }
312
+ | { type: 'LOGIN_STARTED' }
313
+ | { type: 'LOGIN_SUCCESS'; user: User; tokens: TokenInfo }
314
+ | { type: 'LOGIN_ERROR'; error: AuthError }
315
+ | { type: 'LOGOUT_STARTED' }
316
+ | { type: 'LOGOUT_SUCCESS' }
317
+ | { type: 'LOGOUT_ERROR'; error: AuthError }
318
+ | { type: 'TOKEN_REFRESHED'; tokens: TokenInfo }
319
+ | { type: 'TOKEN_REFRESH_ERROR'; error: AuthError }
320
+ | { type: 'SESSION_EXPIRED' };
321
+ ```
322
+
323
+ ---
324
+
325
+ ## 🊝 React Hooks
326
+
327
+ | Hook | Description |
328
+ |------|-------------|
329
+ | `useDouveryAuth()` | Full context with state, actions, and client |
330
+ | `useUser()` | Current user or null |
331
+ | `useIsAuthenticated()` | Boolean authentication status |
332
+ | `useAccessToken()` | `{ accessToken, getAccessToken }` |
333
+ | `useAuthActions()` | `{ login, logout, isLoading }` |
334
+
335
+ ### DouveryAuthProvider Props
336
+
337
+ ```typescript
338
+ interface DouveryAuthProviderProps {
339
+ config: DouveryAuthConfig;
340
+ children: ReactNode;
341
+ client?: DouveryAuthClient; // Optional pre-configured client
342
+ onAuthenticated?: (user: User) => void;
343
+ onLogout?: () => void;
344
+ onError?: (error: Error) => void;
345
+ }
346
+ ```
347
+
348
+ ---
349
+
350
+ ## ⚡ Qwik Hooks
201
351
 
202
- - ✅ **PKCE Support** - Secure authorization code flow with PKCE
203
- - ✅ **Auto Token Refresh** - Automatic token refresh before expiry
204
- - ✅ **Multiple Storage Options** - localStorage, sessionStorage, memory, or cookies
205
- - ✅ **TypeScript First** - Full TypeScript support with comprehensive types
206
- - ✅ **Framework Adapters** - React and Qwik adapters with hooks
207
- - ✅ **Event System** - Subscribe to auth events (login, logout, token refresh, etc.)
208
- - ✅ **SSR Compatible** - Works with server-side rendering
352
+ | Hook | Return Type | Description |
353
+ |------|-------------|-------------|
354
+ | `useDouveryAuth()` | Context | Full context with signals and client |
355
+ | `useUser()` | `Signal<User \| null>` | Reactive user signal |
356
+ | `useIsAuthenticated()` | `Signal<boolean>` | Reactive auth status |
357
+ | `useAuthActions()` | `{ login, logout, isLoading }` | Auth actions |
209
358
 
210
- ## Development
359
+ ---
360
+
361
+ ## 🔒 Security Best Practices
362
+
363
+ ### 1. Always Use HTTPS in Production
364
+ ```typescript
365
+ const config = {
366
+ redirectUri: 'https://yourapp.com/callback', // Not http://
367
+ };
368
+ ```
369
+
370
+ ### 2. Validate Redirect URIs
371
+ Register exact redirect URIs in your OAuth application settings.
372
+
373
+ ### 3. Use Appropriate Storage
374
+ ```typescript
375
+ // For high-security apps, use memory storage
376
+ const auth = createDouveryAuth({
377
+ storage: 'memory', // Tokens cleared on page refresh
378
+ });
379
+
380
+ // For normal apps, localStorage is fine
381
+ const auth = createDouveryAuth({
382
+ storage: 'localStorage', // Persists across tabs/sessions
383
+ });
384
+ ```
385
+
386
+ ### 4. Handle Token Expiry
387
+ ```typescript
388
+ auth.subscribe((event) => {
389
+ if (event.type === 'SESSION_EXPIRED') {
390
+ // Redirect to login or show re-auth modal
391
+ auth.login({ prompt: 'login' });
392
+ }
393
+ });
394
+ ```
395
+
396
+ ---
397
+
398
+ ## 🔧 Handling Callbacks
399
+
400
+ ### React
401
+
402
+ ```tsx
403
+ // pages/callback.tsx or routes/callback.tsx
404
+ import { useEffect } from 'react';
405
+ import { useDouveryAuth } from '@douvery/auth/react';
406
+ import { useNavigate } from 'react-router-dom';
407
+
408
+ export function CallbackPage() {
409
+ const { isInitialized, isAuthenticated, error } = useDouveryAuth();
410
+ const navigate = useNavigate();
411
+
412
+ useEffect(() => {
413
+ if (isInitialized) {
414
+ if (isAuthenticated) {
415
+ navigate('/dashboard');
416
+ } else if (error) {
417
+ navigate('/login?error=' + error.message);
418
+ }
419
+ }
420
+ }, [isInitialized, isAuthenticated, error, navigate]);
421
+
422
+ return <div>Completing login...</div>;
423
+ }
424
+ ```
425
+
426
+ ### Qwik
427
+
428
+ ```tsx
429
+ // routes/callback/index.tsx
430
+ import { component$ } from '@builder.io/qwik';
431
+ import { useNavigate } from '@builder.io/qwik-city';
432
+ import { useDouveryAuth } from '@douvery/auth/qwik';
433
+
434
+ export default component$(() => {
435
+ const { isInitialized, state, error } = useDouveryAuth();
436
+ const nav = useNavigate();
437
+
438
+ useVisibleTask$(({ track }) => {
439
+ track(() => isInitialized.value);
440
+ if (isInitialized.value) {
441
+ if (state.value.status === 'authenticated') {
442
+ nav('/dashboard');
443
+ } else if (error.value) {
444
+ nav('/login?error=' + error.value.message);
445
+ }
446
+ }
447
+ });
448
+
449
+ return <div>Completing login...</div>;
450
+ });
451
+ ```
452
+
453
+ ---
454
+
455
+ ## 🛠ïļ Advanced Usage
456
+
457
+ ### Custom Storage
458
+
459
+ ```typescript
460
+ import { createDouveryAuth, TokenStorage } from '@douvery/auth';
461
+
462
+ const secureStorage: TokenStorage = {
463
+ get: (key) => secureStore.getItem(key),
464
+ set: (key, value) => secureStore.setItem(key, value),
465
+ remove: (key) => secureStore.removeItem(key),
466
+ clear: () => secureStore.clear(),
467
+ };
468
+
469
+ const auth = createDouveryAuth({
470
+ clientId: 'your-client-id',
471
+ redirectUri: '/callback',
472
+ customStorage: secureStorage,
473
+ });
474
+ ```
475
+
476
+ ### Pre-configured Client (React)
477
+
478
+ ```tsx
479
+ import { DouveryAuthProvider } from '@douvery/auth/react';
480
+ import { createDouveryAuth } from '@douvery/auth';
481
+
482
+ // Create client once, outside component
483
+ const authClient = createDouveryAuth({
484
+ clientId: 'your-client-id',
485
+ redirectUri: '/callback',
486
+ });
487
+
488
+ function App() {
489
+ return (
490
+ <DouveryAuthProvider config={{}} client={authClient}>
491
+ <YourApp />
492
+ </DouveryAuthProvider>
493
+ );
494
+ }
495
+ ```
496
+
497
+ ### API Request with Token
498
+
499
+ ```typescript
500
+ async function fetchProtectedData() {
501
+ const token = await auth.getAccessToken();
502
+
503
+ if (!token) {
504
+ throw new Error('Not authenticated');
505
+ }
506
+
507
+ const response = await fetch('https://api.example.com/data', {
508
+ headers: {
509
+ Authorization: `Bearer ${token}`,
510
+ 'Content-Type': 'application/json',
511
+ },
512
+ });
513
+
514
+ if (response.status === 401) {
515
+ // Token might be invalid, try to refresh
516
+ await auth.refreshTokens();
517
+ return fetchProtectedData();
518
+ }
519
+
520
+ return response.json();
521
+ }
522
+ ```
523
+
524
+ ---
525
+
526
+ ## 📁 Package Structure
527
+
528
+ ```
529
+ @douvery/auth
530
+ ├── dist/
531
+ │ ├── index.js # Core (ESM)
532
+ │ ├── index.d.ts # Core types
533
+ │ ├── react/
534
+ │ │ ├── index.js # React adapter
535
+ │ │ └── index.d.ts # React types
536
+ │ └── qwik/
537
+ │ ├── index.js # Qwik adapter
538
+ │ └── index.d.ts # Qwik types
539
+ ```
540
+
541
+ **Imports:**
542
+ ```typescript
543
+ // Core
544
+ import { createDouveryAuth, DouveryAuthClient } from '@douvery/auth';
545
+
546
+ // React
547
+ import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth/react';
548
+
549
+ // Qwik
550
+ import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth/qwik';
551
+ ```
552
+
553
+ ---
554
+
555
+ ## ðŸĪ Contributing
211
556
 
212
557
  ```bash
558
+ # Clone the repository
559
+ git clone https://github.com/douvery/douvery-auth.git
560
+ cd douvery-auth
561
+
213
562
  # Install dependencies
214
- pnpm install
563
+ bun install
215
564
 
216
565
  # Build all packages
217
- pnpm build
566
+ npm run build
567
+
568
+ # Run type checking
569
+ npx tsc --noEmit
570
+ ```
218
571
 
219
- # Run in development mode
220
- pnpm dev
572
+ ---
221
573
 
222
- # Type check
223
- pnpm typecheck
574
+ ## 📄 License
224
575
 
225
- # Clean build outputs
226
- pnpm clean
227
- ```
576
+ MIT ÂĐ [Douvery](https://douvery.com)
228
577
 
229
- ## License
578
+ ---
230
579
 
231
- MIT
580
+ <p align="center">
581
+ Made with âĪïļ by the Douvery team
582
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douvery/auth",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "OAuth 2.0/OIDC client for Douvery authentication",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",