@rqdhw3n/react-auth-flow 1.0.4 → 1.0.6

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 CHANGED
@@ -1,631 +1,459 @@
1
- # @rqdhw3n/react-auth-flow
2
-
3
- A production-ready, TypeScript-first authentication flow package for React applications. Provides a complete solution for handling user authentication with minimal configuration while maintaining flexibility.
4
-
5
- ## Features
6
-
7
- - 🔐 **Secure by default**: HttpOnly cookie JWT support
8
- - **Zero dependencies**: Uses native fetch API
9
- - 🎣 **Custom hooks**: `useAuth()` hook for accessing auth state
10
- - 🔄 **Auto-refresh**: Automatic session refresh capability
11
- - 🎨 **Unstyled components**: Ready-to-use forms with full customization
12
- - 🛣️ **Protected routes**: React Router v6 compatible route protection
13
- - 📦 **TypeScript support**: Full type safety throughout
14
- - 🔌 **Customizable**: Custom headers, endpoints, and request adapters
15
- - 🚀 **SSR-friendly**: Works with server-side rendering when configured properly
16
-
17
- ## Installation
18
-
19
- ```bash
20
- npm install @rqdhw3n/react-auth-flow react react-dom react-router-dom
21
- ```
22
-
23
- or with yarn:
24
-
25
- ```bash
26
- yarn add @rqdhw3n/react-auth-flow react react-dom react-router-dom
27
- ```
28
-
29
- ## Quick Setup
30
-
31
- ### 1. Wrap your app with AuthProvider
32
-
33
- ```tsx
34
- import { AuthProvider } from '@rqdhw3n/react-auth-flow';
35
-
36
- function App() {
37
- return (
38
- <AuthProvider
39
- baseURL="https://api.example.com"
40
- autoRefresh={true}
41
- refreshInterval={5 * 60 * 1000} // 5 minutes
42
- >
43
- {/* Your app components */}
44
- </AuthProvider>
45
- );
46
- }
47
- ```
48
-
49
- ### 2. Use the useAuth hook
50
-
51
- ```tsx
52
- import { useAuth } from '@rqdhw3n/react-auth-flow';
53
-
54
- function Header() {
55
- const { user, isAuthenticated, logout } = useAuth();
56
-
57
- if (!isAuthenticated) {
58
- return <span>Please log in</span>;
59
- }
60
-
61
- return (
62
- <div>
63
- <p>Welcome, {user?.name}</p>
64
- <button onClick={logout}>Logout</button>
65
- </div>
66
- );
67
- }
68
- ```
69
-
70
- ## Usage Examples
71
-
72
- ### LoginForm Component with Tailwind CSS
73
-
74
- ```tsx
75
- import { LoginForm } from '@rqdhw3n/react-auth-flow';
76
- import { useNavigate } from 'react-router-dom';
77
-
78
- function LoginPage() {
79
- const navigate = useNavigate();
80
-
81
- return (
82
- <div className="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-12">
83
- <LoginForm
84
- title="Welcome back"
85
- subtitle="Sign in to your account"
86
- className="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-8 shadow-lg"
87
- formClassName="space-y-5"
88
- fieldClassName="space-y-2"
89
- labelClassName="block text-sm font-medium text-slate-700"
90
- inputClassName="w-full rounded-lg border border-slate-300 px-4 py-2 text-sm focus:border-blue-500 focus:ring-4 focus:ring-blue-100"
91
- buttonClassName="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700"
92
- submitButtonText="Sign In"
93
- loadingText="Signing in..."
94
- labels={{
95
- email: 'Email Address',
96
- password: 'Password',
97
- rememberMe: 'Keep me signed in',
98
- }}
99
- placeholders={{
100
- email: 'you@example.com',
101
- password: 'Enter your password',
102
- }}
103
- onSuccess={() => navigate('/dashboard')}
104
- onError={(error) => console.error(error.message)}
105
- />
106
- </div>
107
- );
108
- }
109
- ```
110
-
111
- ### RegisterForm Component with Tailwind CSS
112
-
113
- ```tsx
114
- import { RegisterForm } from '@rqdhw3n/react-auth-flow';
115
- import { useNavigate, Link } from 'react-router-dom';
116
-
117
- function SignUpPage() {
118
- const navigate = useNavigate();
119
-
120
- return (
121
- <div className="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-12">
122
- <div className="w-full max-w-md">
123
- <RegisterForm
124
- title="Create your account"
125
- subtitle="Get started in seconds"
126
- className="rounded-2xl border border-slate-200 bg-white p-8 shadow-lg"
127
- formClassName="space-y-5"
128
- fieldClassName="space-y-2"
129
- labelClassName="block text-sm font-medium text-slate-700"
130
- inputClassName="w-full rounded-lg border border-slate-300 px-4 py-2 text-sm focus:border-blue-500 focus:ring-4 focus:ring-blue-100"
131
- buttonClassName="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700"
132
- submitButtonText="Create Account"
133
- loadingText="Creating..."
134
- labels={{
135
- name: 'Full Name',
136
- email: 'Email',
137
- password: 'Password',
138
- confirmPassword: 'Confirm Password',
139
- }}
140
- onSuccess={() => navigate('/email-verify')}
141
- />
142
- <p className="mt-4 text-center text-sm text-slate-600">
143
- Already have an account?{' '}
144
- <Link to="/login" className="font-semibold text-blue-600 hover:text-blue-700">
145
- Sign in
146
- </Link>
147
- </p>
148
- </div>
149
- </div>
150
- );
151
- }
152
- ```
153
-
154
- ### ForgotPasswordForm Component
155
-
156
- ```tsx
157
- import { ForgotPasswordForm } from '@rqdhw3n/react-auth-flow';
158
-
159
- function ForgotPasswordPage() {
160
- return (
161
- <div className="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-12">
162
- <ForgotPasswordForm
163
- title="Reset your password"
164
- subtitle="Enter your email to receive a reset link"
165
- className="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-8 shadow-lg"
166
- formClassName="space-y-5"
167
- fieldClassName="space-y-2"
168
- labelClassName="block text-sm font-medium text-slate-700"
169
- inputClassName="w-full rounded-lg border border-slate-300 px-4 py-2 text-sm focus:border-blue-500 focus:ring-4 focus:ring-blue-100"
170
- buttonClassName="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700"
171
- errorClassName="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600"
172
- successClassName="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700"
173
- submitButtonText="Send Reset Link"
174
- loadingText="Sending..."
175
- onSuccess={() => {
176
- alert('Check your email for reset link');
177
- }}
178
- />
179
- </div>
180
- );
181
- }
182
- ```
183
-
184
- ### ResetPasswordForm Component
185
-
186
- ```tsx
187
- import { ResetPasswordForm } from '@rqdhw3n/react-auth-flow';
188
- import { useSearchParams, useNavigate } from 'react-router-dom';
189
-
190
- function ResetPasswordPage() {
191
- const [searchParams] = useSearchParams();
192
- const navigate = useNavigate();
193
- const token = searchParams.get('token') || '';
194
-
195
- return (
196
- <div className="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-12">
197
- <ResetPasswordForm
198
- token={token}
199
- title="Create new password"
200
- subtitle="Enter your new password below"
201
- className="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-8 shadow-lg"
202
- formClassName="space-y-5"
203
- fieldClassName="space-y-2"
204
- labelClassName="block text-sm font-medium text-slate-700"
205
- inputClassName="w-full rounded-lg border border-slate-300 px-4 py-2 text-sm focus:border-blue-500 focus:ring-4 focus:ring-blue-100"
206
- buttonClassName="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700"
207
- submitButtonText="Update Password"
208
- loadingText="Updating..."
209
- onSuccess={() => navigate('/login')}
210
- />
211
- </div>
212
- );
213
- }
214
- ```
215
-
216
- ### VerifyEmailForm Component
217
-
218
- ```tsx
219
- import { VerifyEmailForm } from '@rqdhw3n/react-auth-flow';
220
- import { useSearchParams } from 'react-router-dom';
221
-
222
- function VerifyEmailPage() {
223
- const [searchParams] = useSearchParams();
224
- const token = searchParams.get('token') || '';
225
- const email = searchParams.get('email') || '';
226
-
227
- return (
228
- <div className="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-12">
229
- <VerifyEmailForm
230
- token={token}
231
- email={email}
232
- title="Verify your email"
233
- subtitle="Enter the code sent to your email address"
234
- className="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-8 shadow-lg"
235
- formClassName="space-y-5"
236
- fieldClassName="space-y-2"
237
- labelClassName="block text-sm font-medium text-slate-700"
238
- inputClassName="w-full rounded-lg border border-slate-300 px-4 py-2 text-sm focus:border-blue-500 focus:ring-4 focus:ring-blue-100"
239
- buttonClassName="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700"
240
- submitButtonText="Verify Email"
241
- loadingText="Verifying..."
242
- onSuccess={() => {
243
- alert('Email verified! You can now log in.');
244
- }}
245
- />
246
- </div>
247
- );
248
- }
249
- ```
250
-
251
- ### Simple Usage Without Custom Styling
252
-
253
- ```tsx
254
- // Forms work with default Tailwind-friendly styling
255
- <LoginForm
256
- onSuccess={() => navigate('/dashboard')}
257
- />
258
- ```
259
-
260
- ### ProtectedRoute Component
261
-
262
- ```tsx
263
- import { ProtectedRoute } from '@rqdhw3n/react-auth-flow';
264
- import { Routes, Route } from 'react-router-dom';
265
-
266
- function App() {
267
- return (
268
- <Routes>
269
- <Route path="/login" element={<LoginPage />} />
270
- <Route
271
- path="/dashboard"
272
- element={
273
- <ProtectedRoute redirectTo="/login">
274
- <Dashboard />
275
- </ProtectedRoute>
276
- }
277
- />
278
- <Route
279
- path="/admin"
280
- element={
281
- <ProtectedRoute
282
- roles={['admin']}
283
- redirectTo="/unauthorized"
284
- >
285
- <AdminPanel />
286
- </ProtectedRoute>
287
- }
288
- />
289
- <Route
290
- path="/manage-users"
291
- element={
292
- <ProtectedRoute
293
- permissions={['users.manage', 'users.delete']}
294
- redirectTo="/unauthorized"
295
- >
296
- <UserManagement />
297
- </ProtectedRoute>
298
- }
299
- />
300
- </Routes>
301
- );
302
- }
303
- ```
304
-
305
- ### useAuth Hook
306
-
307
- ```tsx
308
- import { useAuth } from '@rqdhw3n/react-auth-flow';
309
-
310
- function UserProfile() {
311
- const {
312
- user,
313
- isAuthenticated,
314
- isLoading,
315
- error,
316
- login,
317
- logout,
318
- refreshSession,
319
- setUser,
320
- } = useAuth();
321
-
322
- const handleLogin = async () => {
323
- try {
324
- const user = await login({
325
- email: 'user@example.com',
326
- password: 'password',
327
- rememberMe: true,
328
- });
329
- console.log('Logged in as:', user);
330
- } catch (err) {
331
- console.error('Login failed:', err);
332
- }
333
- };
334
-
335
- const handleLogout = async () => {
336
- await logout();
337
- console.log('Logged out');
338
- };
339
-
340
- if (isLoading) {
341
- return <div>Loading...</div>;
342
- }
343
-
344
- if (error) {
345
- return <div>Error: {error.message}</div>;
346
- }
347
-
348
- if (!isAuthenticated) {
349
- return <button onClick={handleLogin}>Login</button>;
350
- }
351
-
352
- return (
353
- <div>
354
- <h1>Hello, {user?.name}</h1>
355
- <p>Email: {user?.email}</p>
356
- <p>Roles: {user?.roles?.join(', ')}</p>
357
- <button onClick={handleLogout}>Logout</button>
358
- <button onClick={refreshSession}>Refresh Session</button>
359
- </div>
360
- );
361
- }
362
- ```
363
-
364
- ## API Endpoints Configuration
365
-
366
- The package expects your backend to provide the following endpoints by default:
367
-
368
- ### Default Endpoints
369
-
370
- - `POST /auth/login` - Login with email and password
371
- - `POST /auth/register` - Register a new account
372
- - `POST /auth/logout` - Logout the user
373
- - `GET /auth/me` - Get current user information
374
- - `POST /auth/refresh` - Refresh authentication token
375
- - `POST /auth/forgot-password` - Request password reset
376
- - `POST /auth/reset-password` - Reset password with token
377
- - `POST /auth/verify-email` - Verify email with token
378
-
379
- ### Customizing Endpoints
380
-
381
- ```tsx
382
- import { AuthProvider } from '@rqdhw3n/react-auth-flow';
383
-
384
- <AuthProvider
385
- baseURL="https://api.example.com"
386
- endpoints={{
387
- login: '/auth/signin',
388
- register: '/auth/signup',
389
- me: '/user/profile',
390
- // ... other endpoints
391
- }}
392
- >
393
- {/* Your app */}
394
- </AuthProvider>
395
- ```
396
-
397
- ## Expected API Response Formats
398
-
399
- ### Login Response
400
-
401
- ```json
402
- {
403
- "user": {
404
- "id": 1,
405
- "name": "John Doe",
406
- "email": "john@example.com",
407
- "roles": ["admin"],
408
- "permissions": ["users.manage", "users.delete"]
409
- }
410
- }
411
- ```
412
-
413
- ### Current User (Me) Response
414
-
415
- ```json
416
- {
417
- "user": {
418
- "id": 1,
419
- "name": "John Doe",
420
- "email": "john@example.com",
421
- "roles": ["admin"],
422
- "permissions": ["users.manage", "users.delete"]
423
- }
424
- }
425
- ```
426
-
427
- ### Error Response
428
-
429
- ```json
430
- {
431
- "error": {
432
- "code": "INVALID_CREDENTIALS",
433
- "message": "Invalid email or password",
434
- "statusCode": 401
435
- }
436
- }
437
- ```
438
-
439
- ## HttpOnly Cookie JWT Authentication
440
-
441
- For enhanced security, configure your backend to use HttpOnly cookies:
442
-
443
- ```tsx
444
- // Backend should set a cookie like:
445
- // Set-Cookie: jwt=token; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
446
-
447
- // The package will automatically include cookies with requests
448
- // because credentials: "include" is set by default
449
- ```
450
-
451
- ## Custom Headers and Request Adapter
452
-
453
- ```tsx
454
- import { createAuthClient } from '@rqdhw3n/react-auth-flow';
455
-
456
- // Create a custom client
457
- const authClient = createAuthClient({
458
- baseURL: 'https://api.example.com',
459
- headers: {
460
- 'X-API-Key': 'your-api-key',
461
- 'X-Client-Version': '1.0.0',
462
- },
463
- credentials: 'include', // for HttpOnly cookies
464
- adapter: async (url, options) => {
465
- // Custom request logic
466
- console.log('Making request to:', url);
467
- return fetch(url, options);
468
- },
469
- });
470
-
471
- // Use in AuthProvider
472
- <AuthProvider
473
- baseURL="https://api.example.com"
474
- // ... other config
475
- >
476
- {/* Your app */}
477
- </AuthProvider>
478
- ```
479
-
480
- ## TypeScript Customization
481
-
482
- Extend types for your application:
483
-
484
- ```tsx
485
- import { AuthUser, AuthContextValue } from '@rqdhw3n/react-auth-flow';
486
-
487
- // Extend the AuthUser type
488
- interface AppUser extends AuthUser {
489
- id: number;
490
- name: string;
491
- email: string;
492
- roles: string[];
493
- permissions: string[];
494
- company?: string;
495
- department?: string;
496
- }
497
-
498
- // Use in your components
499
- import { useAuth } from '@rqdhw3n/react-auth-flow';
500
-
501
- function MyComponent() {
502
- const { user } = useAuth();
503
- const appUser = user as AppUser;
504
-
505
- return <div>Working in {appUser.department}</div>;
506
- }
507
- ```
508
-
509
- ## Styling Components
510
-
511
- All form components use semantic class names for styling:
512
-
513
- ```css
514
- .auth-form-group {
515
- margin-bottom: 1rem;
516
- }
517
-
518
- .auth-form-label {
519
- display: block;
520
- margin-bottom: 0.5rem;
521
- font-weight: 500;
522
- }
523
-
524
- .auth-form-input {
525
- width: 100%;
526
- padding: 0.5rem;
527
- border: 1px solid #ccc;
528
- border-radius: 4px;
529
- font-size: 1rem;
530
- }
531
-
532
- .auth-form-input:disabled {
533
- background-color: #f5f5f5;
534
- cursor: not-allowed;
535
- }
536
-
537
- .auth-form-error {
538
- color: #dc3545;
539
- margin-top: 0.5rem;
540
- font-size: 0.875rem;
541
- }
542
-
543
- .auth-form-button {
544
- padding: 0.5rem 1rem;
545
- border: none;
546
- border-radius: 4px;
547
- font-size: 1rem;
548
- cursor: pointer;
549
- width: 100%;
550
- }
551
-
552
- .auth-form-button-primary {
553
- background-color: #007bff;
554
- color: white;
555
- }
556
-
557
- .auth-form-button-primary:hover {
558
- background-color: #0056b3;
559
- }
560
-
561
- .auth-form-button:disabled {
562
- opacity: 0.5;
563
- cursor: not-allowed;
564
- }
565
-
566
- .auth-form-checkbox {
567
- display: flex;
568
- align-items: center;
569
- gap: 0.5rem;
570
- }
571
-
572
- .auth-form-checkbox .auth-form-label {
573
- margin-bottom: 0;
574
- }
575
-
576
- .auth-form-success {
577
- padding: 1rem;
578
- background-color: #d4edda;
579
- border: 1px solid #c3e6cb;
580
- border-radius: 4px;
581
- color: #155724;
582
- }
583
-
584
- .auth-form-success-message {
585
- margin: 0;
586
- }
587
-
588
- .auth-loading {
589
- text-align: center;
590
- padding: 2rem;
591
- font-size: 1rem;
592
- }
593
-
594
- .auth-forbidden {
595
- padding: 2rem;
596
- text-align: center;
597
- color: #dc3545;
598
- }
599
- ```
600
-
601
- ## Error Handling
602
-
603
- The package normalizes all errors to a consistent format:
604
-
605
- ```tsx
606
- import { useAuth, AuthError } from '@rqdhw3n/react-auth-flow';
607
-
608
- function MyComponent() {
609
- const { error } = useAuth();
610
-
611
- if (error) {
612
- const authError: AuthError = error;
613
- console.log({
614
- code: authError.code, // e.g., "INVALID_CREDENTIALS"
615
- message: authError.message,
616
- statusCode: authError.statusCode, // e.g., 401
617
- details: authError.details, // additional error info
618
- });
619
- }
620
-
621
- return <div>Status: {error?.message}</div>;
622
- }
623
- ```
624
-
625
- ## License
626
-
627
- MIT
628
-
629
- ## Support
630
-
631
- For issues and feature requests, please visit the [GitHub repository](https://github.com/rqdhw3n/react-auth-flow).
1
+ # @rqdhw3n/react-auth-flow
2
+
3
+ Reusable auth flows for React apps with packaged UI, route guards, mock mode, theming, and cookie-friendly request handling.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @rqdhw3n/react-auth-flow react react-dom react-router-dom
9
+ ```
10
+
11
+ The package injects its own auth CSS automatically. Consumers do not need Tailwind or a manual CSS import.
12
+
13
+ ## Basic setup
14
+
15
+ ```tsx
16
+ import {
17
+ AuthLayout,
18
+ AuthProvider,
19
+ LoginForm,
20
+ ProtectedRoute,
21
+ } from "@rqdhw3n/react-auth-flow";
22
+ import { BrowserRouter, Route, Routes } from "react-router-dom";
23
+
24
+ function LoginPage() {
25
+ return (
26
+ <AuthLayout
27
+ title="Welcome back"
28
+ subtitle="Sign in to continue"
29
+ brand={<strong>Acme</strong>}
30
+ >
31
+ <LoginForm />
32
+ </AuthLayout>
33
+ );
34
+ }
35
+
36
+ function App() {
37
+ return (
38
+ <BrowserRouter>
39
+ <AuthProvider baseURL="http://localhost:8000/api">
40
+ <Routes>
41
+ <Route path="/login" element={<LoginPage />} />
42
+ <Route
43
+ path="/dashboard"
44
+ element={
45
+ <ProtectedRoute redirectTo="/login">
46
+ <div>Dashboard</div>
47
+ </ProtectedRoute>
48
+ }
49
+ />
50
+ </Routes>
51
+ </AuthProvider>
52
+ </BrowserRouter>
53
+ );
54
+ }
55
+ ```
56
+
57
+ `baseURL` and `baseUrl` are both supported.
58
+
59
+ ## Mock mode local test
60
+
61
+ Use mock mode when you want the package to work without a backend.
62
+
63
+ ```tsx
64
+ import { AuthProvider } from "@rqdhw3n/react-auth-flow";
65
+
66
+ export function Root() {
67
+ return (
68
+ <AuthProvider mock mockStorageKey="rq-auth-user">
69
+ <App />
70
+ </AuthProvider>
71
+ );
72
+ }
73
+ ```
74
+
75
+ Mock behavior:
76
+
77
+ - `login()` creates a fake admin user and stores it in `localStorage`
78
+ - `register()` creates a fake normal user and stores it in `localStorage`
79
+ - `logout()` clears the fake session
80
+ - `restoreSession()` and `me()` read the fake session back
81
+ - `forgotPassword()`, `resetPassword()`, and `verifyEmail()` return success
82
+ - `verifyTwoFactor()` accepts any code with the configured length
83
+
84
+ Custom mock user:
85
+
86
+ ```tsx
87
+ <AuthProvider
88
+ mock
89
+ mockStorageKey="rq-auth-user"
90
+ mockUser={{
91
+ id: 99,
92
+ name: "Demo User",
93
+ email: "demo@example.com",
94
+ roles: ["admin"],
95
+ permissions: ["users.manage", "billing.edit"],
96
+ }}
97
+ >
98
+ <App />
99
+ </AuthProvider>
100
+ ```
101
+
102
+ ## Custom requestAdapter
103
+
104
+ If `mock` is `true`, mock mode wins. Otherwise the provider uses:
105
+
106
+ 1. `requestAdapter`
107
+ 2. built-in `fetch`
108
+
109
+ ```tsx
110
+ import { AuthProvider, type AuthRequestAdapter } from "@rqdhw3n/react-auth-flow";
111
+ import axios from "axios";
112
+
113
+ const requestAdapter: AuthRequestAdapter = async ({
114
+ endpoint,
115
+ method,
116
+ data,
117
+ headers,
118
+ }) => {
119
+ const response = await axios({
120
+ url: `http://localhost:8000/api${endpoint}`,
121
+ method,
122
+ data,
123
+ withCredentials: true,
124
+ headers,
125
+ });
126
+
127
+ return response.data;
128
+ };
129
+
130
+ <AuthProvider requestAdapter={requestAdapter}>
131
+ <App />
132
+ </AuthProvider>;
133
+ ```
134
+
135
+ ## Theme customization
136
+
137
+ Theme values are applied through CSS variables on the provider wrapper.
138
+
139
+ ```tsx
140
+ <AuthProvider
141
+ baseURL="http://localhost:8000/api"
142
+ theme={{
143
+ primaryColor: "#0f766e",
144
+ primaryHoverColor: "#115e59",
145
+ radius: "20px",
146
+ fontFamily: "'Manrope', sans-serif",
147
+ }}
148
+ >
149
+ <App />
150
+ </AuthProvider>
151
+ ```
152
+
153
+ Available theme keys:
154
+
155
+ - `primaryColor`
156
+ - `primaryHoverColor`
157
+ - `radius`
158
+ - `fontFamily`
159
+
160
+ ## AuthLayout usage
161
+
162
+ ```tsx
163
+ import { AuthLayout, LoginForm } from "@rqdhw3n/react-auth-flow";
164
+
165
+ function LoginPage() {
166
+ return (
167
+ <AuthLayout
168
+ title="Welcome back"
169
+ subtitle="Login to continue"
170
+ brand={<img src="/logo.svg" alt="Brand" height={32} />}
171
+ footer={<a href="/register">Create account</a>}
172
+ >
173
+ <LoginForm />
174
+ </AuthLayout>
175
+ );
176
+ }
177
+ ```
178
+
179
+ ## Login/Register/Forgot/Reset forms
180
+
181
+ Included components:
182
+
183
+ - `LoginForm`
184
+ - `RegisterForm`
185
+ - `ForgotPasswordForm`
186
+ - `ResetPasswordForm`
187
+ - `VerifyEmailForm`
188
+
189
+ Example:
190
+
191
+ ```tsx
192
+ import {
193
+ ForgotPasswordForm,
194
+ LoginForm,
195
+ RegisterForm,
196
+ ResetPasswordForm,
197
+ VerifyEmailForm,
198
+ } from "@rqdhw3n/react-auth-flow";
199
+
200
+ <LoginForm onSuccess={(user) => console.log("logged in", user)} />;
201
+
202
+ <RegisterForm onSuccess={(user) => console.log("registered", user)} />;
203
+
204
+ <ForgotPasswordForm onSuccess={() => console.log("email sent")} />;
205
+
206
+ <ResetPasswordForm token="reset-token-from-url" />;
207
+
208
+ <VerifyEmailForm token="email-token" email="hello@example.com" />;
209
+ ```
210
+
211
+ ## ProtectedRoute
212
+
213
+ ```tsx
214
+ import { ProtectedRoute } from "@rqdhw3n/react-auth-flow";
215
+
216
+ <ProtectedRoute
217
+ redirectTo="/login"
218
+ unauthorizedTo="/forbidden"
219
+ roles={["admin"]}
220
+ permissions={["users.manage", "billing.edit"]}
221
+ requireAllPermissions={true}
222
+ fallback={<div>Checking session...</div>}
223
+ >
224
+ <AdminDashboard />
225
+ </ProtectedRoute>;
226
+ ```
227
+
228
+ Behavior:
229
+
230
+ - loading: renders `fallback`
231
+ - not authenticated: redirects to `redirectTo`
232
+ - authenticated but unauthorized: redirects to `unauthorizedTo` or `redirectTo`
233
+ - `requireAllPermissions={false}` switches permission checks to "any match"
234
+
235
+ ## GuestRoute
236
+
237
+ ```tsx
238
+ import { GuestRoute } from "@rqdhw3n/react-auth-flow";
239
+
240
+ <GuestRoute redirectTo="/" fallback={<div>Loading...</div>}>
241
+ <LoginPage />
242
+ </GuestRoute>;
243
+ ```
244
+
245
+ Authenticated users are redirected away from guest-only pages like login and register.
246
+
247
+ ## Roles and permissions helpers
248
+
249
+ `useAuth()` now exposes:
250
+
251
+ - `hasRole(role)`
252
+ - `hasAnyRole(roles)`
253
+ - `hasPermission(permission)`
254
+ - `hasAnyPermission(permissions)`
255
+ - `hasAllPermissions(permissions)`
256
+ - `refreshSession()`
257
+ - `restoreSession()`
258
+ - `clearError()`
259
+ - `setError()`
260
+
261
+ ```tsx
262
+ import { useAuth } from "@rqdhw3n/react-auth-flow";
263
+
264
+ function Toolbar() {
265
+ const { user, hasRole, hasAnyPermission, logout } = useAuth();
266
+
267
+ return (
268
+ <div>
269
+ <span>{user?.name}</span>
270
+ {hasRole("admin") && <button>Admin</button>}
271
+ {hasAnyPermission(["billing.edit", "users.manage"]) && (
272
+ <button>Manage</button>
273
+ )}
274
+ <button onClick={logout}>Logout</button>
275
+ </div>
276
+ );
277
+ }
278
+ ```
279
+
280
+ ## OTP / 2FA
281
+
282
+ Included components:
283
+
284
+ - `OtpInput`
285
+ - `TwoFactorForm`
286
+
287
+ Provider support:
288
+
289
+ - endpoint: `twoFactorVerify`, default `/auth/2fa/verify`
290
+ - hook method: `verifyTwoFactor({ code })`
291
+
292
+ ```tsx
293
+ import { TwoFactorForm } from "@rqdhw3n/react-auth-flow";
294
+
295
+ <TwoFactorForm
296
+ length={6}
297
+ title="Enter your code"
298
+ subtitle="Check your authenticator app"
299
+ onSuccess={(response) => console.log(response)}
300
+ />;
301
+ ```
302
+
303
+ Using the hook directly:
304
+
305
+ ```tsx
306
+ import { useAuth } from "@rqdhw3n/react-auth-flow";
307
+
308
+ function VerifyButton() {
309
+ const { verifyTwoFactor } = useAuth();
310
+
311
+ return (
312
+ <button onClick={() => verifyTwoFactor({ code: "123456" })}>
313
+ Verify
314
+ </button>
315
+ );
316
+ }
317
+ ```
318
+
319
+ ## SocialLoginButton
320
+
321
+ UI-only social button component. No OAuth flow is built in.
322
+
323
+ ```tsx
324
+ import { SocialLoginButton } from "@rqdhw3n/react-auth-flow";
325
+
326
+ <SocialLoginButton provider="google" onClick={() => startGoogleLogin()} />;
327
+ <SocialLoginButton provider="github" label="Continue with GitHub" />;
328
+ <SocialLoginButton provider="custom" icon={<span>SAML</span>} label="SSO" />;
329
+ ```
330
+
331
+ ## HttpOnly cookie JWT backend example
332
+
333
+ This package works well with backends that issue cookies instead of exposing tokens to the browser.
334
+
335
+ ```ts
336
+ // Example request adapter when the backend uses HttpOnly cookies
337
+ const requestAdapter = async ({ endpoint, method, data, headers }) => {
338
+ const response = await fetch(`https://api.example.com${endpoint}`, {
339
+ method,
340
+ credentials: "include",
341
+ headers: {
342
+ "Content-Type": "application/json",
343
+ ...headers,
344
+ },
345
+ body: data ? JSON.stringify(data) : undefined,
346
+ });
347
+
348
+ if (!response.ok) {
349
+ throw await response.json();
350
+ }
351
+
352
+ return response.status === 204 ? {} : await response.json();
353
+ };
354
+ ```
355
+
356
+ Expected backend flow:
357
+
358
+ - `POST /auth/login` sets the auth cookie and returns `{ user }`
359
+ - `GET /auth/me` returns `{ user }`
360
+ - `POST /auth/logout` clears the cookie
361
+ - `POST /auth/refresh` rotates the session and returns `{ user }`
362
+
363
+ ## Laravel example
364
+
365
+ ```php
366
+ // routes/api.php
367
+ Route::post('/auth/login', [AuthController::class, 'login']);
368
+ Route::post('/auth/register', [AuthController::class, 'register']);
369
+ Route::middleware('auth:sanctum')->group(function () {
370
+ Route::get('/auth/me', [AuthController::class, 'me']);
371
+ Route::post('/auth/logout', [AuthController::class, 'logout']);
372
+ Route::post('/auth/refresh', [AuthController::class, 'refresh']);
373
+ Route::post('/auth/2fa/verify', [AuthController::class, 'verifyTwoFactor']);
374
+ });
375
+ Route::post('/auth/forgot-password', [PasswordController::class, 'forgot']);
376
+ Route::post('/auth/reset-password', [PasswordController::class, 'reset']);
377
+ Route::post('/auth/verify-email', [VerificationController::class, 'verify']);
378
+ ```
379
+
380
+ Typical provider setup with Sanctum:
381
+
382
+ ```tsx
383
+ <AuthProvider baseURL="http://localhost:8000/api">
384
+ <App />
385
+ </AuthProvider>
386
+ ```
387
+
388
+ ## Express fake backend example
389
+
390
+ ```ts
391
+ import cookieParser from "cookie-parser";
392
+ import express from "express";
393
+
394
+ const app = express();
395
+
396
+ app.use(express.json());
397
+ app.use(cookieParser());
398
+
399
+ app.post("/api/auth/login", (_req, res) => {
400
+ res.cookie("session", "demo-session", { httpOnly: true, sameSite: "lax" });
401
+ res.json({
402
+ user: {
403
+ id: 1,
404
+ name: "Admin Test",
405
+ email: "admin@example.com",
406
+ roles: ["admin"],
407
+ permissions: ["users.manage", "billing.edit"],
408
+ },
409
+ });
410
+ });
411
+
412
+ app.get("/api/auth/me", (req, res) => {
413
+ if (!req.cookies.session) {
414
+ return res.status(401).json({
415
+ error: { code: "UNAUTHENTICATED", message: "Unauthenticated" },
416
+ });
417
+ }
418
+
419
+ return res.json({
420
+ user: {
421
+ id: 1,
422
+ name: "Admin Test",
423
+ email: "admin@example.com",
424
+ roles: ["admin"],
425
+ permissions: ["users.manage", "billing.edit"],
426
+ },
427
+ });
428
+ });
429
+
430
+ app.post("/api/auth/logout", (_req, res) => {
431
+ res.clearCookie("session");
432
+ res.status(204).end();
433
+ });
434
+
435
+ app.post("/api/auth/forgot-password", (_req, res) => {
436
+ res.json({ success: true, message: "Reset email queued" });
437
+ });
438
+
439
+ app.post("/api/auth/reset-password", (_req, res) => {
440
+ res.json({ success: true, message: "Password updated" });
441
+ });
442
+
443
+ app.post("/api/auth/verify-email", (_req, res) => {
444
+ res.json({ success: true, message: "Email verified" });
445
+ });
446
+
447
+ app.post("/api/auth/2fa/verify", (_req, res) => {
448
+ res.json({ success: true, message: "2FA verified" });
449
+ });
450
+
451
+ app.listen(8000);
452
+ ```
453
+
454
+ ## Notes
455
+
456
+ - `AuthProvider` restores the session on mount by default. Disable with `autoRestore={false}`.
457
+ - React 18 and React 19 are supported through peer dependencies.
458
+ - React, React DOM, React Router, and `react/jsx-runtime` are externalized from the build.
459
+ - The published package includes `dist/style.css` and also injects the auth CSS automatically from the package entry.