@rqdhw3n/react-auth-flow 1.0.5 → 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,17 +1,6 @@
1
1
  # @rqdhw3n/react-auth-flow
2
2
 
3
- A TypeScript-first authentication flow package for React applications with built-in, production-ready auth form styling.
4
-
5
- ## Features
6
-
7
- - Secure by default with cookie-based auth support
8
- - Zero UI framework dependency
9
- - Built-in stylesheet loaded automatically from the package entry
10
- - Ready-to-use `LoginForm`, `RegisterForm`, `ForgotPasswordForm`, `ResetPasswordForm`, and `VerifyEmailForm`
11
- - `AuthProvider` and `useAuth()` for auth state management
12
- - `ProtectedRoute` support for React Router
13
- - Customizable endpoints, headers, and request adapters
14
- - Override-friendly class name props for consumer styling
3
+ Reusable auth flows for React apps with packaged UI, route guards, mock mode, theming, and cookie-friendly request handling.
15
4
 
16
5
  ## Installation
17
6
 
@@ -19,243 +8,452 @@ A TypeScript-first authentication flow package for React applications with built
19
8
  npm install @rqdhw3n/react-auth-flow react react-dom react-router-dom
20
9
  ```
21
10
 
22
- No Tailwind, PostCSS, `tailwind.config.js`, or manual CSS import is required. Importing the package automatically loads the packaged auth stylesheet.
23
-
24
- ## Quick Setup
11
+ The package injects its own auth CSS automatically. Consumers do not need Tailwind or a manual CSS import.
25
12
 
26
- ### Wrap your app with `AuthProvider`
13
+ ## Basic setup
27
14
 
28
15
  ```tsx
29
- import { AuthProvider } from "@rqdhw3n/react-auth-flow";
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";
30
23
 
31
- function App() {
24
+ function LoginPage() {
32
25
  return (
33
- <AuthProvider
34
- baseURL="https://api.example.com"
35
- autoRefresh={true}
36
- refreshInterval={5 * 60 * 1000}
26
+ <AuthLayout
27
+ title="Welcome back"
28
+ subtitle="Sign in to continue"
29
+ brand={<strong>Acme</strong>}
37
30
  >
38
- {/* your app */}
39
- </AuthProvider>
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>
40
53
  );
41
54
  }
42
55
  ```
43
56
 
44
- ### Use `useAuth()`
57
+ `baseURL` and `baseUrl` are both supported.
45
58
 
46
- ```tsx
47
- import { useAuth } from "@rqdhw3n/react-auth-flow";
59
+ ## Mock mode local test
48
60
 
49
- function Header() {
50
- const { user, isAuthenticated, logout } = useAuth();
61
+ Use mock mode when you want the package to work without a backend.
51
62
 
52
- if (!isAuthenticated) {
53
- return <span>Please log in</span>;
54
- }
63
+ ```tsx
64
+ import { AuthProvider } from "@rqdhw3n/react-auth-flow";
55
65
 
66
+ export function Root() {
56
67
  return (
57
- <div>
58
- <p>Welcome, {user?.name}</p>
59
- <button onClick={logout}>Logout</button>
60
- </div>
68
+ <AuthProvider mock mockStorageKey="rq-auth-user">
69
+ <App />
70
+ </AuthProvider>
61
71
  );
62
72
  }
63
73
  ```
64
74
 
65
- ## Forms
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:
66
105
 
67
- ### Styled out of the box
106
+ 1. `requestAdapter`
107
+ 2. built-in `fetch`
68
108
 
69
109
  ```tsx
70
- import { LoginForm } from "@rqdhw3n/react-auth-flow";
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
+ ```
71
134
 
72
- function App() {
73
- return <LoginForm />;
74
- }
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>
75
151
  ```
76
152
 
77
- ### Login form
153
+ Available theme keys:
154
+
155
+ - `primaryColor`
156
+ - `primaryHoverColor`
157
+ - `radius`
158
+ - `fontFamily`
159
+
160
+ ## AuthLayout usage
78
161
 
79
162
  ```tsx
80
- import { LoginForm } from "@rqdhw3n/react-auth-flow";
81
- import { useNavigate } from "react-router-dom";
163
+ import { AuthLayout, LoginForm } from "@rqdhw3n/react-auth-flow";
82
164
 
83
165
  function LoginPage() {
84
- const navigate = useNavigate();
85
-
86
166
  return (
87
- <div
88
- style={{
89
- minHeight: "100vh",
90
- display: "grid",
91
- placeItems: "center",
92
- padding: 24,
93
- background: "#f4f7fb",
94
- }}
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>}
95
172
  >
96
- <LoginForm
97
- title="Welcome back"
98
- subtitle="Sign in to continue"
99
- submitButtonText="Sign In"
100
- loadingText="Signing in..."
101
- labels={{
102
- email: "Email Address",
103
- password: "Password",
104
- rememberMe: "Keep me signed in",
105
- }}
106
- onSuccess={() => navigate("/dashboard")}
107
- />
108
- </div>
173
+ <LoginForm />
174
+ </AuthLayout>
109
175
  );
110
176
  }
111
177
  ```
112
178
 
113
- ### Register form
179
+ ## Login/Register/Forgot/Reset forms
180
+
181
+ Included components:
182
+
183
+ - `LoginForm`
184
+ - `RegisterForm`
185
+ - `ForgotPasswordForm`
186
+ - `ResetPasswordForm`
187
+ - `VerifyEmailForm`
188
+
189
+ Example:
114
190
 
115
191
  ```tsx
116
- import { RegisterForm } from "@rqdhw3n/react-auth-flow";
192
+ import {
193
+ ForgotPasswordForm,
194
+ LoginForm,
195
+ RegisterForm,
196
+ ResetPasswordForm,
197
+ VerifyEmailForm,
198
+ } from "@rqdhw3n/react-auth-flow";
117
199
 
118
- function SignUpPage() {
119
- return (
120
- <RegisterForm
121
- title="Create your account"
122
- subtitle="Get started in seconds"
123
- submitButtonText="Create Account"
124
- loadingText="Creating account..."
125
- />
126
- );
127
- }
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" />;
128
209
  ```
129
210
 
130
- ### Forgot password form
211
+ ## ProtectedRoute
131
212
 
132
213
  ```tsx
133
- import { ForgotPasswordForm } from "@rqdhw3n/react-auth-flow";
214
+ import { ProtectedRoute } from "@rqdhw3n/react-auth-flow";
134
215
 
135
- function ForgotPasswordPage() {
136
- return (
137
- <ForgotPasswordForm
138
- title="Reset your password"
139
- subtitle="Enter your email to receive a reset link"
140
- />
141
- );
142
- }
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>;
143
226
  ```
144
227
 
145
- ### Reset password form
228
+ Behavior:
146
229
 
147
- ```tsx
148
- import { ResetPasswordForm } from "@rqdhw3n/react-auth-flow";
149
- import { useSearchParams } from "react-router-dom";
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"
150
234
 
151
- function ResetPasswordPage() {
152
- const [searchParams] = useSearchParams();
153
- const token = searchParams.get("token") || "";
235
+ ## GuestRoute
154
236
 
155
- return (
156
- <ResetPasswordForm
157
- token={token}
158
- title="Create a new password"
159
- subtitle="Use at least 8 characters"
160
- />
161
- );
162
- }
237
+ ```tsx
238
+ import { GuestRoute } from "@rqdhw3n/react-auth-flow";
239
+
240
+ <GuestRoute redirectTo="/" fallback={<div>Loading...</div>}>
241
+ <LoginPage />
242
+ </GuestRoute>;
163
243
  ```
164
244
 
165
- ### Verify email form
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()`
166
260
 
167
261
  ```tsx
168
- import { VerifyEmailForm } from "@rqdhw3n/react-auth-flow";
169
- import { useSearchParams } from "react-router-dom";
262
+ import { useAuth } from "@rqdhw3n/react-auth-flow";
170
263
 
171
- function VerifyEmailPage() {
172
- const [searchParams] = useSearchParams();
264
+ function Toolbar() {
265
+ const { user, hasRole, hasAnyPermission, logout } = useAuth();
173
266
 
174
267
  return (
175
- <VerifyEmailForm
176
- token={searchParams.get("token") || ""}
177
- email={searchParams.get("email") || ""}
178
- title="Verify your email"
179
- subtitle="Enter the verification code you received"
180
- />
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>
181
276
  );
182
277
  }
183
278
  ```
184
279
 
185
- ## Styling Overrides
280
+ ## OTP / 2FA
186
281
 
187
- The package ships with default classes and also supports consumer overrides through props:
282
+ Included components:
188
283
 
189
- - `className`
190
- - `formClassName`
191
- - `fieldClassName`
192
- - `labelClassName`
193
- - `inputClassName`
194
- - `buttonClassName`
195
- - `errorClassName`
196
- - `successClassName`
197
- - `titleClassName`
198
- - `subtitleClassName`
284
+ - `OtpInput`
285
+ - `TwoFactorForm`
199
286
 
200
- Example:
287
+ Provider support:
288
+
289
+ - endpoint: `twoFactorVerify`, default `/auth/2fa/verify`
290
+ - hook method: `verifyTwoFactor({ code })`
201
291
 
202
292
  ```tsx
203
- import { LoginForm } from "@rqdhw3n/react-auth-flow";
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();
204
310
 
205
- function CustomLoginPage() {
206
311
  return (
207
- <LoginForm
208
- className="my-auth-card"
209
- formClassName="my-auth-form"
210
- labelClassName="my-auth-label"
211
- inputClassName="my-auth-input"
212
- buttonClassName="my-auth-button"
213
- errorClassName="my-auth-error"
214
- successClassName="my-auth-success"
215
- />
312
+ <button onClick={() => verifyTwoFactor({ code: "123456" })}>
313
+ Verify
314
+ </button>
216
315
  );
217
316
  }
218
317
  ```
219
318
 
220
- Default packaged class names:
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
+ ```
221
355
 
222
- - `.rq-auth-container`
223
- - `.rq-auth-form`
224
- - `.rq-auth-field`
225
- - `.rq-auth-label`
226
- - `.rq-auth-input`
227
- - `.rq-auth-button`
228
- - `.rq-auth-error`
229
- - `.rq-auth-success`
230
- - `.rq-auth-title`
231
- - `.rq-auth-subtitle`
232
- - `.rq-auth-checkbox`
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
+ ```
233
379
 
234
- ## Protected Routes
380
+ Typical provider setup with Sanctum:
235
381
 
236
382
  ```tsx
237
- import { ProtectedRoute } from "@rqdhw3n/react-auth-flow";
238
- import { Routes, Route } from "react-router-dom";
383
+ <AuthProvider baseURL="http://localhost:8000/api">
384
+ <App />
385
+ </AuthProvider>
386
+ ```
239
387
 
240
- function AppRoutes() {
241
- return (
242
- <Routes>
243
- <Route path="/login" element={<LoginPage />} />
244
- <Route
245
- path="/dashboard"
246
- element={
247
- <ProtectedRoute redirectTo="/login">
248
- <Dashboard />
249
- </ProtectedRoute>
250
- }
251
- />
252
- </Routes>
253
- );
254
- }
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);
255
452
  ```
256
453
 
257
454
  ## Notes
258
455
 
259
- - The package stylesheet is imported from `src/index.ts`, so consumers do not need `import "./styles.css"` in their apps.
260
- - Build output includes JavaScript bundles and an emitted CSS asset in `dist`.
261
- - The components are responsive by default and work on desktop and mobile without extra setup.
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.