@douvery/auth 0.1.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.
- package/README.md +440 -89
- package/dist/index.js +36 -9
- package/dist/index.js.map +1 -1
- package/dist/qwik/index.d.ts +197 -0
- package/dist/qwik/index.js +813 -0
- package/dist/qwik/index.js.map +1 -0
- package/dist/react/index.d.ts +212 -0
- package/dist/react/index.js +868 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +68 -1
package/README.md
CHANGED
|
@@ -1,88 +1,114 @@
|
|
|
1
1
|
# @douvery/auth
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>ð OAuth 2.0/OIDC client library for Douvery authentication</strong>
|
|
12
|
+
</p>
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
18
|
+
---
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
## âĻ Features
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
33
|
+
---
|
|
24
34
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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
|
|
84
|
+
// 2. Use hooks in your components
|
|
63
85
|
function LoginButton() {
|
|
64
|
-
const { login, logout, isAuthenticated,
|
|
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
|
|
83
|
-
import { component
|
|
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
|
|
127
|
+
// 2. Use hooks in your components (signal-based)
|
|
102
128
|
export const LoginButton = component$(() => {
|
|
103
|
-
const
|
|
129
|
+
const user = useUser();
|
|
130
|
+
const { login, logout, isLoading } = useAuthActions();
|
|
104
131
|
|
|
105
132
|
return (
|
|
106
133
|
<>
|
|
107
|
-
{
|
|
134
|
+
{user.value ? (
|
|
108
135
|
<div>
|
|
109
|
-
<
|
|
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()}>
|
|
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: '
|
|
158
|
+
redirectUri: window.location.origin + '/callback',
|
|
128
159
|
issuer: 'https://auth.douvery.com',
|
|
129
160
|
scopes: ['openid', 'profile', 'email'],
|
|
130
161
|
});
|
|
131
162
|
|
|
132
|
-
//
|
|
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
|
-
//
|
|
136
|
-
|
|
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
|
-
//
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
166
|
-
scopes?: string[];
|
|
222
|
+
/** OAuth scopes to request */
|
|
223
|
+
scopes?: string[]; // default: ["openid", "profile", "email"]
|
|
167
224
|
|
|
168
|
-
/** Token storage strategy
|
|
169
|
-
storage?: "localStorage" | "sessionStorage" | "memory" | "cookie";
|
|
225
|
+
/** Token storage strategy */
|
|
226
|
+
storage?: "localStorage" | "sessionStorage" | "memory" | "cookie"; // default: "localStorage"
|
|
170
227
|
|
|
171
|
-
/**
|
|
172
|
-
|
|
228
|
+
/** Custom storage implementation */
|
|
229
|
+
customStorage?: TokenStorage;
|
|
173
230
|
|
|
174
|
-
/**
|
|
175
|
-
|
|
231
|
+
/** Auto-refresh tokens before expiry */
|
|
232
|
+
autoRefresh?: boolean; // default: true
|
|
176
233
|
|
|
177
|
-
/**
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
+
bun install
|
|
215
564
|
|
|
216
565
|
# Build all packages
|
|
217
|
-
|
|
566
|
+
npm run build
|
|
567
|
+
|
|
568
|
+
# Run type checking
|
|
569
|
+
npx tsc --noEmit
|
|
570
|
+
```
|
|
218
571
|
|
|
219
|
-
|
|
220
|
-
pnpm dev
|
|
572
|
+
---
|
|
221
573
|
|
|
222
|
-
|
|
223
|
-
pnpm typecheck
|
|
574
|
+
## ð License
|
|
224
575
|
|
|
225
|
-
|
|
226
|
-
pnpm clean
|
|
227
|
-
```
|
|
576
|
+
MIT ÂĐ [Douvery](https://douvery.com)
|
|
228
577
|
|
|
229
|
-
|
|
578
|
+
---
|
|
230
579
|
|
|
231
|
-
|
|
580
|
+
<p align="center">
|
|
581
|
+
Made with âĪïļ by the Douvery team
|
|
582
|
+
</p>
|