@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 +371 -173
- package/dist/index.cjs.js +779 -210
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +126 -65
- package/dist/index.es.js +744 -175
- package/dist/index.es.js.map +1 -1
- package/dist/style.css +209 -47
- package/package.json +24 -20
package/README.md
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
# @rqdhw3n/react-auth-flow
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
+
## Basic setup
|
|
27
14
|
|
|
28
15
|
```tsx
|
|
29
|
-
import {
|
|
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
|
|
24
|
+
function LoginPage() {
|
|
32
25
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
<AuthLayout
|
|
27
|
+
title="Welcome back"
|
|
28
|
+
subtitle="Sign in to continue"
|
|
29
|
+
brand={<strong>Acme</strong>}
|
|
37
30
|
>
|
|
38
|
-
|
|
39
|
-
</
|
|
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
|
-
|
|
57
|
+
`baseURL` and `baseUrl` are both supported.
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
import { useAuth } from "@rqdhw3n/react-auth-flow";
|
|
59
|
+
## Mock mode local test
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
const { user, isAuthenticated, logout } = useAuth();
|
|
61
|
+
Use mock mode when you want the package to work without a backend.
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
63
|
+
```tsx
|
|
64
|
+
import { AuthProvider } from "@rqdhw3n/react-auth-flow";
|
|
55
65
|
|
|
66
|
+
export function Root() {
|
|
56
67
|
return (
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
</div>
|
|
68
|
+
<AuthProvider mock mockStorageKey="rq-auth-user">
|
|
69
|
+
<App />
|
|
70
|
+
</AuthProvider>
|
|
61
71
|
);
|
|
62
72
|
}
|
|
63
73
|
```
|
|
64
74
|
|
|
65
|
-
|
|
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
|
-
|
|
106
|
+
1. `requestAdapter`
|
|
107
|
+
2. built-in `fetch`
|
|
68
108
|
|
|
69
109
|
```tsx
|
|
70
|
-
import {
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
192
|
+
import {
|
|
193
|
+
ForgotPasswordForm,
|
|
194
|
+
LoginForm,
|
|
195
|
+
RegisterForm,
|
|
196
|
+
ResetPasswordForm,
|
|
197
|
+
VerifyEmailForm,
|
|
198
|
+
} from "@rqdhw3n/react-auth-flow";
|
|
117
199
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
211
|
+
## ProtectedRoute
|
|
131
212
|
|
|
132
213
|
```tsx
|
|
133
|
-
import {
|
|
214
|
+
import { ProtectedRoute } from "@rqdhw3n/react-auth-flow";
|
|
134
215
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
228
|
+
Behavior:
|
|
146
229
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
const [searchParams] = useSearchParams();
|
|
153
|
-
const token = searchParams.get("token") || "";
|
|
235
|
+
## GuestRoute
|
|
154
236
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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 {
|
|
169
|
-
import { useSearchParams } from "react-router-dom";
|
|
262
|
+
import { useAuth } from "@rqdhw3n/react-auth-flow";
|
|
170
263
|
|
|
171
|
-
function
|
|
172
|
-
const
|
|
264
|
+
function Toolbar() {
|
|
265
|
+
const { user, hasRole, hasAnyPermission, logout } = useAuth();
|
|
173
266
|
|
|
174
267
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
##
|
|
280
|
+
## OTP / 2FA
|
|
186
281
|
|
|
187
|
-
|
|
282
|
+
Included components:
|
|
188
283
|
|
|
189
|
-
- `
|
|
190
|
-
- `
|
|
191
|
-
- `fieldClassName`
|
|
192
|
-
- `labelClassName`
|
|
193
|
-
- `inputClassName`
|
|
194
|
-
- `buttonClassName`
|
|
195
|
-
- `errorClassName`
|
|
196
|
-
- `successClassName`
|
|
197
|
-
- `titleClassName`
|
|
198
|
-
- `subtitleClassName`
|
|
284
|
+
- `OtpInput`
|
|
285
|
+
- `TwoFactorForm`
|
|
199
286
|
|
|
200
|
-
|
|
287
|
+
Provider support:
|
|
288
|
+
|
|
289
|
+
- endpoint: `twoFactorVerify`, default `/auth/2fa/verify`
|
|
290
|
+
- hook method: `verifyTwoFactor({ code })`
|
|
201
291
|
|
|
202
292
|
```tsx
|
|
203
|
-
import {
|
|
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
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
380
|
+
Typical provider setup with Sanctum:
|
|
235
381
|
|
|
236
382
|
```tsx
|
|
237
|
-
|
|
238
|
-
|
|
383
|
+
<AuthProvider baseURL="http://localhost:8000/api">
|
|
384
|
+
<App />
|
|
385
|
+
</AuthProvider>
|
|
386
|
+
```
|
|
239
387
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
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.
|