antaeus.keycloak.react 1.0.6 → 2.0.0
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 +135 -546
- package/dist/index.d.mts +307 -11
- package/dist/index.d.ts +307 -11
- package/dist/index.js +711 -125
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +709 -128
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,652 +1,241 @@
|
|
|
1
1
|
# antaeus.keycloak.react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/antaeus.keycloak.react)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
|
|
8
|
-
## Features
|
|
9
|
-
|
|
10
|
-
### Authentication & Authorization
|
|
11
|
-
- ✅ **Authorization Code + PKCE** - Secure authentication flow
|
|
12
|
-
- ✅ **Device Flow Support** - For TVs, IoT devices, and CLIs
|
|
13
|
-
- ✅ **Protected Routes** - Easy route protection with role checking
|
|
14
|
-
- ✅ **Automatic Token Refresh** - Silent token renewal
|
|
15
|
-
- ✅ **Role-Based Access Control** - Built-in role checking utilities
|
|
16
|
-
|
|
17
|
-
### UI Components
|
|
18
|
-
- ✅ **Pre-built Components** - LoginButton, LogoutButton, UserProfile
|
|
19
|
-
- ✅ **Session Monitor** - Real-time session countdown with warnings
|
|
20
|
-
- ✅ **Device Login UI** - QR code display with automatic polling
|
|
21
|
-
- ✅ **Professional Styling** - Production-ready CSS with dark mode
|
|
22
|
-
|
|
23
|
-
### Developer Experience
|
|
24
|
-
- ✅ **TypeScript Support** - Full type safety
|
|
25
|
-
- ✅ **Minimal Configuration** - Works with sensible defaults
|
|
26
|
-
- ✅ **Protected Fetch Hook** - API calls with automatic token injection
|
|
27
|
-
- ✅ **Tiny Bundle** - Only 28 KB (minified, before gzip)
|
|
3
|
+
Production-ready Keycloak integration for React with zero-config setup
|
|
28
4
|
|
|
29
5
|
## Installation
|
|
30
6
|
|
|
31
7
|
```bash
|
|
32
8
|
npm install antaeus.keycloak.react
|
|
33
|
-
# or
|
|
34
|
-
yarn add antaeus.keycloak.react
|
|
35
|
-
# or
|
|
36
|
-
pnpm add antaeus.keycloak.react
|
|
37
9
|
```
|
|
38
10
|
|
|
39
|
-
## Quick Start
|
|
40
|
-
|
|
41
|
-
### 1. Import styles and wrap your app with KeycloakProvider
|
|
11
|
+
## Quick Start (Zero-Config)
|
|
42
12
|
|
|
43
13
|
```tsx
|
|
44
|
-
// main.tsx
|
|
45
14
|
import { KeycloakProvider } from 'antaeus.keycloak.react';
|
|
46
|
-
import 'antaeus.keycloak.react/styles.css'; // Import CSS
|
|
47
15
|
|
|
48
16
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
49
17
|
<KeycloakProvider
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
clientId: 'my-app',
|
|
53
|
-
}}
|
|
18
|
+
authority="https://sso.example.com/realms/myrealm"
|
|
19
|
+
clientId="my-app"
|
|
54
20
|
>
|
|
55
21
|
<App />
|
|
56
22
|
</KeycloakProvider>
|
|
57
23
|
);
|
|
58
24
|
```
|
|
59
25
|
|
|
60
|
-
|
|
26
|
+
That's it! You now have full authentication with automatic token refresh.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- Zero-config setup
|
|
31
|
+
- Automatic token lifecycle management
|
|
32
|
+
- Token refresh on app startup
|
|
33
|
+
- Silent token renewal
|
|
34
|
+
- Session monitoring UI
|
|
35
|
+
- Protected routes
|
|
36
|
+
- API hooks with auto-auth
|
|
37
|
+
- Device flow support
|
|
38
|
+
- TypeScript support
|
|
39
|
+
|
|
40
|
+
## Usage in Components
|
|
61
41
|
|
|
62
42
|
```tsx
|
|
63
|
-
|
|
64
|
-
import { useAuth, LoginButton, LogoutButton, UserProfile } from 'antaeus.keycloak.react';
|
|
43
|
+
import { useAuth, useProtectedFetch } from 'antaeus.keycloak.react';
|
|
65
44
|
|
|
66
|
-
function
|
|
45
|
+
function MyComponent() {
|
|
67
46
|
const auth = useAuth();
|
|
47
|
+
const { fetchJson } = useProtectedFetch({ baseUrl: 'https://api.example.com' });
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!auth.isAuthenticated) {
|
|
74
|
-
return (
|
|
75
|
-
<div>
|
|
76
|
-
<h1>Welcome to My App</h1>
|
|
77
|
-
<LoginButton variant="primary" size="large">
|
|
78
|
-
Get Started
|
|
79
|
-
</LoginButton>
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
49
|
+
const loadData = async () => {
|
|
50
|
+
const data = await fetchJson('/api/data'); // Token added automatically
|
|
51
|
+
console.log(data);
|
|
52
|
+
};
|
|
83
53
|
|
|
84
|
-
return
|
|
85
|
-
<div>
|
|
86
|
-
<header>
|
|
87
|
-
<UserProfile showAvatar showEmail showRoles />
|
|
88
|
-
<LogoutButton />
|
|
89
|
-
</header>
|
|
90
|
-
<main>
|
|
91
|
-
<h1>Welcome, {auth.user?.profile?.name}</h1>
|
|
92
|
-
<p>You are logged in!</p>
|
|
93
|
-
</main>
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
54
|
+
return <button onClick={loadData}>Load</button>;
|
|
96
55
|
}
|
|
97
56
|
```
|
|
98
57
|
|
|
99
|
-
|
|
58
|
+
## Configuration Modes
|
|
59
|
+
|
|
60
|
+
### 1. Zero-Config (Recommended)
|
|
100
61
|
|
|
101
62
|
```tsx
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function App() {
|
|
106
|
-
return (
|
|
107
|
-
<BrowserRouter>
|
|
108
|
-
<Routes>
|
|
109
|
-
<Route path="/" element={<HomePage />} />
|
|
110
|
-
|
|
111
|
-
<Route
|
|
112
|
-
path="/dashboard"
|
|
113
|
-
element={
|
|
114
|
-
<ProtectedRoute>
|
|
115
|
-
<DashboardPage />
|
|
116
|
-
</ProtectedRoute>
|
|
117
|
-
}
|
|
118
|
-
/>
|
|
119
|
-
|
|
120
|
-
<Route
|
|
121
|
-
path="/admin"
|
|
122
|
-
element={
|
|
123
|
-
<ProtectedRoute requiredRoles={['admin']}>
|
|
124
|
-
<AdminPage />
|
|
125
|
-
</ProtectedRoute>
|
|
126
|
-
}
|
|
127
|
-
/>
|
|
128
|
-
</Routes>
|
|
129
|
-
</BrowserRouter>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
63
|
+
<KeycloakProvider authority="..." clientId="my-app">
|
|
64
|
+
<App />
|
|
65
|
+
</KeycloakProvider>
|
|
132
66
|
```
|
|
133
67
|
|
|
134
|
-
|
|
68
|
+
### 2. With Preset
|
|
135
69
|
|
|
136
|
-
|
|
70
|
+
```tsx
|
|
71
|
+
<KeycloakProvider
|
|
72
|
+
authority="..."
|
|
73
|
+
clientId="my-app"
|
|
74
|
+
preset="mobile" // 'default' | 'mobile' | 'high-security' | 'long-running'
|
|
75
|
+
>
|
|
76
|
+
<App />
|
|
77
|
+
</KeycloakProvider>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. With Overrides
|
|
137
81
|
|
|
138
82
|
```tsx
|
|
139
83
|
<KeycloakProvider
|
|
84
|
+
authority="..."
|
|
85
|
+
clientId="my-app"
|
|
140
86
|
config={{
|
|
141
|
-
|
|
142
|
-
|
|
87
|
+
silentRenew: {
|
|
88
|
+
maxRefreshTokenLifetime: 604800, // 7 days
|
|
89
|
+
showLoadingIndicator: true,
|
|
90
|
+
}
|
|
143
91
|
}}
|
|
144
92
|
>
|
|
145
93
|
<App />
|
|
146
94
|
</KeycloakProvider>
|
|
147
95
|
```
|
|
148
96
|
|
|
149
|
-
###
|
|
97
|
+
### 4. Full Configuration
|
|
150
98
|
|
|
151
99
|
```tsx
|
|
152
100
|
<KeycloakProvider
|
|
153
101
|
config={{
|
|
154
102
|
authority: 'https://sso.example.com/realms/myrealm',
|
|
155
103
|
clientId: 'my-app',
|
|
156
|
-
redirectUri: window.location.origin,
|
|
157
|
-
postLogoutRedirectUri: window.location.origin,
|
|
158
104
|
scope: 'openid profile email offline_access',
|
|
159
|
-
|
|
160
|
-
// Feature flags
|
|
161
|
-
features: {
|
|
162
|
-
autoRefresh: true,
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
// Silent token renewal
|
|
166
105
|
silentRenew: {
|
|
167
|
-
|
|
168
|
-
|
|
106
|
+
refreshStrategy: 'both',
|
|
107
|
+
maxRefreshTokenLifetime: 2592000,
|
|
169
108
|
},
|
|
170
|
-
|
|
171
|
-
// Event callbacks
|
|
172
109
|
events: {
|
|
173
|
-
onLogin: (user) => console.log('
|
|
174
|
-
|
|
175
|
-
onTokenExpired: () => console.log('Token expired'),
|
|
176
|
-
onError: (error) => console.error('Auth error', error),
|
|
177
|
-
},
|
|
110
|
+
onLogin: (user) => console.log('Logged in', user),
|
|
111
|
+
}
|
|
178
112
|
}}
|
|
179
|
-
debug={true} // Enable debug logging
|
|
180
113
|
>
|
|
181
114
|
<App />
|
|
182
115
|
</KeycloakProvider>
|
|
183
116
|
```
|
|
184
117
|
|
|
185
|
-
##
|
|
186
|
-
|
|
187
|
-
### Components
|
|
188
|
-
|
|
189
|
-
#### `KeycloakProvider`
|
|
190
|
-
|
|
191
|
-
Main provider component that wraps your application.
|
|
192
|
-
|
|
193
|
-
**Props:**
|
|
194
|
-
- `config` (required): Keycloak configuration
|
|
195
|
-
- `children` (required): Child components
|
|
196
|
-
- `loadingComponent` (optional): Custom loading component
|
|
197
|
-
- `errorComponent` (optional): Custom error component
|
|
198
|
-
- `debug` (optional): Enable debug logging
|
|
118
|
+
## Configuration Presets
|
|
199
119
|
|
|
200
|
-
|
|
120
|
+
| Preset | Session Lifetime | Best For |
|
|
121
|
+
|--------|------------------|----------|
|
|
122
|
+
| default | 30 days | Most web apps |
|
|
123
|
+
| mobile | 30 days | PWA/Mobile apps |
|
|
124
|
+
| high-security | 24 hours | Banking, healthcare |
|
|
125
|
+
| long-running | 7 days | Desktop apps |
|
|
201
126
|
|
|
202
|
-
|
|
127
|
+
## Token Refresh Strategies
|
|
203
128
|
|
|
204
|
-
**
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
- `requireAllRoles` (optional): Require all roles (AND logic) vs any role (OR logic)
|
|
208
|
-
- `loadingComponent` (optional): Custom loading component
|
|
209
|
-
- `unauthorizedComponent` (optional): Custom unauthorized component
|
|
210
|
-
- `redirectTo` (optional): Redirect path when not authenticated (default: '/login')
|
|
211
|
-
- `onUnauthorized` (optional): Callback when access is denied
|
|
129
|
+
- **startup**: Check on app startup
|
|
130
|
+
- **on-demand**: Only when API returns 401
|
|
131
|
+
- **both** (recommended): Startup + 401 retry
|
|
212
132
|
|
|
213
|
-
|
|
133
|
+
## React Hooks
|
|
214
134
|
|
|
215
|
-
|
|
135
|
+
### useAuth()
|
|
216
136
|
|
|
217
|
-
Main authentication hook.
|
|
218
|
-
|
|
219
|
-
**Returns:**
|
|
220
|
-
- `user`: Current user object
|
|
221
|
-
- `isAuthenticated`: Whether user is authenticated
|
|
222
|
-
- `isLoading`: Whether authentication is in progress
|
|
223
|
-
- `error`: Authentication error if any
|
|
224
|
-
- `signinRedirect()`: Trigger login redirect
|
|
225
|
-
- `signoutRedirect()`: Trigger logout redirect
|
|
226
|
-
- And more...
|
|
227
|
-
|
|
228
|
-
#### `useRoles()`
|
|
229
|
-
|
|
230
|
-
Role checking utilities.
|
|
231
|
-
|
|
232
|
-
**Returns:**
|
|
233
|
-
- `roles`: Array of user's realm roles
|
|
234
|
-
- `hasRole(role)`: Check if user has a specific role
|
|
235
|
-
- `hasAnyRole(roles)`: Check if user has any of the roles
|
|
236
|
-
- `hasAllRoles(roles)`: Check if user has all the roles
|
|
237
|
-
- `getClientRoles(clientId)`: Get client-specific roles
|
|
238
|
-
|
|
239
|
-
#### `useProtectedFetch()`
|
|
240
|
-
|
|
241
|
-
Hook for making authenticated API requests with automatic token injection.
|
|
242
|
-
|
|
243
|
-
**Options:**
|
|
244
|
-
- `baseUrl` (optional): Base URL for API requests
|
|
245
|
-
- `onUnauthorized` (optional): Callback on 401 errors
|
|
246
|
-
- `retryOn401` (optional): Retry after token refresh (default: true)
|
|
247
|
-
- `headers` (optional): Additional headers for all requests
|
|
248
|
-
|
|
249
|
-
**Returns:**
|
|
250
|
-
- `fetchJson<T>()`: Fetch and parse JSON with type safety
|
|
251
|
-
- `fetch()`: Raw fetch with token injection
|
|
252
|
-
- `isLoading`: Whether authentication is loading
|
|
253
|
-
|
|
254
|
-
**Example:**
|
|
255
137
|
```tsx
|
|
256
|
-
const
|
|
257
|
-
baseUrl: 'http://localhost:5000',
|
|
258
|
-
});
|
|
138
|
+
const auth = useAuth();
|
|
259
139
|
|
|
260
|
-
|
|
140
|
+
auth.isAuthenticated
|
|
141
|
+
auth.isLoading
|
|
142
|
+
auth.user
|
|
143
|
+
auth.signinRedirect()
|
|
144
|
+
auth.signoutRedirect()
|
|
261
145
|
```
|
|
262
146
|
|
|
263
|
-
###
|
|
264
|
-
|
|
265
|
-
#### `LoginButton`
|
|
266
|
-
|
|
267
|
-
Pre-built login button with multiple variants.
|
|
268
|
-
|
|
269
|
-
**Props:**
|
|
270
|
-
- `variant`: 'primary' | 'secondary' | 'outline' | 'danger' (default: 'primary')
|
|
271
|
-
- `size`: 'small' | 'medium' | 'large' (default: 'medium')
|
|
272
|
-
- `showIcon`: boolean (default: true)
|
|
273
|
-
- `onLoginStart`: Callback before login
|
|
274
|
-
- `disabled`: boolean
|
|
275
|
-
- `className`: Custom CSS class
|
|
276
|
-
- `style`: Inline styles
|
|
277
|
-
|
|
278
|
-
#### `LogoutButton`
|
|
279
|
-
|
|
280
|
-
Pre-built logout button.
|
|
281
|
-
|
|
282
|
-
**Props:**
|
|
283
|
-
- Same as LoginButton
|
|
284
|
-
|
|
285
|
-
#### `UserProfile`
|
|
286
|
-
|
|
287
|
-
Displays user information with avatar, name, email, and roles.
|
|
288
|
-
|
|
289
|
-
**Props:**
|
|
290
|
-
- `showAvatar`: boolean (default: true)
|
|
291
|
-
- `showEmail`: boolean (default: true)
|
|
292
|
-
- `showRoles`: boolean (default: false)
|
|
293
|
-
- `avatarSize`: number (default: 40)
|
|
294
|
-
- `maxRoles`: number (default: 3)
|
|
295
|
-
- `className`: Custom CSS class
|
|
296
|
-
- `style`: Inline styles
|
|
147
|
+
### useProtectedFetch()
|
|
297
148
|
|
|
298
|
-
#### `SessionMonitor`
|
|
299
|
-
|
|
300
|
-
Real-time session countdown timer with visual warnings.
|
|
301
|
-
|
|
302
|
-
**Props:**
|
|
303
|
-
- `position`: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' (default: 'top-right')
|
|
304
|
-
- `warningThreshold`: Seconds for yellow warning (default: 300)
|
|
305
|
-
- `criticalThreshold`: Seconds for red warning (default: 60)
|
|
306
|
-
- `showProgressBar`: boolean (default: true)
|
|
307
|
-
- `minimizable`: boolean (default: true)
|
|
308
|
-
- `startMinimized`: boolean (default: false)
|
|
309
|
-
- `onWarning`: Callback at warning threshold
|
|
310
|
-
- `onCritical`: Callback at critical threshold
|
|
311
|
-
- `onExpired`: Callback when session expires
|
|
312
|
-
|
|
313
|
-
**Example:**
|
|
314
149
|
```tsx
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
onCritical={(seconds) => alert(`Session expiring in ${seconds} seconds!`)}
|
|
320
|
-
/>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
#### `DeviceLogin`
|
|
324
|
-
|
|
325
|
-
Device flow login component with QR code display.
|
|
326
|
-
|
|
327
|
-
**Props:**
|
|
328
|
-
- `apiBaseUrl`: Backend API URL (required)
|
|
329
|
-
- `qrCodeSize`: QR code size in pixels (default: 256)
|
|
330
|
-
- `pollingInterval`: Polling interval in seconds (default: 5)
|
|
331
|
-
- `onSuccess`: Callback on successful login
|
|
332
|
-
- `onError`: Callback on error
|
|
333
|
-
- `className`: Custom CSS class
|
|
334
|
-
- `style`: Inline styles
|
|
150
|
+
const { fetchJson, isLoading } = useProtectedFetch({
|
|
151
|
+
baseUrl: 'https://api.example.com',
|
|
152
|
+
retryOn401: true,
|
|
153
|
+
});
|
|
335
154
|
|
|
336
|
-
|
|
337
|
-
```tsx
|
|
338
|
-
<DeviceLogin
|
|
339
|
-
apiBaseUrl="http://localhost:5000"
|
|
340
|
-
onSuccess={(user) => navigate('/dashboard')}
|
|
341
|
-
/>
|
|
155
|
+
const data = await fetchJson('/api/data');
|
|
342
156
|
```
|
|
343
157
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
### Role-Based Access
|
|
158
|
+
### useRoles()
|
|
347
159
|
|
|
348
160
|
```tsx
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
function MyComponent() {
|
|
352
|
-
const { roles, hasRole, hasAnyRole } = useRoles();
|
|
161
|
+
const { hasRole, hasAnyRole } = useRoles();
|
|
353
162
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<p>Your roles: {roles.join(', ')}</p>
|
|
357
|
-
|
|
358
|
-
{hasRole('admin') && <AdminPanel />}
|
|
359
|
-
|
|
360
|
-
{hasAnyRole(['editor', 'moderator']) && <EditButton />}
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
163
|
+
if (hasRole('admin')) {
|
|
164
|
+
return <AdminPanel />;
|
|
363
165
|
}
|
|
364
166
|
```
|
|
365
167
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
```tsx
|
|
369
|
-
// User needs at least one of these roles
|
|
370
|
-
<ProtectedRoute requiredRoles={['editor', 'moderator', 'admin']}>
|
|
371
|
-
<EditPage />
|
|
372
|
-
</ProtectedRoute>
|
|
373
|
-
|
|
374
|
-
// User needs ALL of these roles
|
|
375
|
-
<ProtectedRoute requiredRoles={['verified', 'premium']} requireAllRoles={true}>
|
|
376
|
-
<PremiumContent />
|
|
377
|
-
</ProtectedRoute>
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
### Custom Loading/Error States
|
|
381
|
-
|
|
382
|
-
```tsx
|
|
383
|
-
<ProtectedRoute
|
|
384
|
-
loadingComponent={<Spinner />}
|
|
385
|
-
unauthorizedComponent={<AccessDenied />}
|
|
386
|
-
>
|
|
387
|
-
<ProtectedContent />
|
|
388
|
-
</ProtectedRoute>
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Authenticated API Calls
|
|
168
|
+
## Protected Routes
|
|
392
169
|
|
|
393
170
|
```tsx
|
|
394
|
-
import {
|
|
395
|
-
|
|
396
|
-
function UsersList() {
|
|
397
|
-
const { fetchJson, isLoading } = useProtectedFetch({
|
|
398
|
-
baseUrl: 'http://localhost:5000',
|
|
399
|
-
});
|
|
400
|
-
const [users, setUsers] = useState([]);
|
|
401
|
-
|
|
402
|
-
useEffect(() => {
|
|
403
|
-
fetchJson<User[]>('/api/users')
|
|
404
|
-
.then(setUsers)
|
|
405
|
-
.catch(console.error);
|
|
406
|
-
}, []);
|
|
407
|
-
|
|
408
|
-
if (isLoading) return <div>Loading...</div>;
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<ul>
|
|
412
|
-
{users.map(user => (
|
|
413
|
-
<li key={user.id}>{user.name}</li>
|
|
414
|
-
))}
|
|
415
|
-
</ul>
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### Session Monitoring
|
|
171
|
+
import { ProtectedRoute } from 'antaeus.keycloak.react';
|
|
421
172
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
{/* Monitor session and warn before expiration */}
|
|
431
|
-
<SessionMonitor
|
|
432
|
-
position="top-right"
|
|
433
|
-
warningThreshold={600} // 10 minutes
|
|
434
|
-
criticalThreshold={120} // 2 minutes
|
|
435
|
-
onWarning={() => console.log('Session expiring soon')}
|
|
436
|
-
onCritical={() => {
|
|
437
|
-
// Show modal or notification
|
|
438
|
-
alert('Your session is about to expire!');
|
|
439
|
-
}}
|
|
440
|
-
/>
|
|
441
|
-
</>
|
|
442
|
-
);
|
|
443
|
-
}
|
|
173
|
+
<Route
|
|
174
|
+
path="/dashboard"
|
|
175
|
+
element={
|
|
176
|
+
<ProtectedRoute>
|
|
177
|
+
<DashboardPage />
|
|
178
|
+
</ProtectedRoute>
|
|
179
|
+
}
|
|
180
|
+
/>
|
|
444
181
|
```
|
|
445
182
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
183
|
+
## Configuration Reference
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface KeycloakConfig {
|
|
187
|
+
authority: string; // Required
|
|
188
|
+
clientId: string; // Required
|
|
189
|
+
scope?: string; // Default: 'openid profile email offline_access'
|
|
190
|
+
|
|
191
|
+
features?: {
|
|
192
|
+
autoRefresh?: boolean; // Default: true
|
|
193
|
+
sessionMonitor?: boolean; // Default: true
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
silentRenew?: {
|
|
197
|
+
enabled?: boolean; // Default: true
|
|
198
|
+
thresholdSeconds?: number; // Default: 300
|
|
199
|
+
refreshStrategy?: 'startup' | 'on-demand' | 'both';
|
|
200
|
+
maxRefreshTokenLifetime?: number; // Default: 2592000 (30 days)
|
|
201
|
+
showLoadingIndicator?: boolean; // Default: false
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
events?: {
|
|
205
|
+
onLogin?: (user: User) => void;
|
|
206
|
+
onLogout?: () => void;
|
|
207
|
+
onTokenExpired?: () => void;
|
|
208
|
+
onSilentRenewError?: (error: Error) => void;
|
|
209
|
+
};
|
|
469
210
|
}
|
|
470
211
|
```
|
|
471
212
|
|
|
472
|
-
|
|
213
|
+
## What's New in v2.0.0
|
|
473
214
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
```tsx
|
|
481
|
-
import { getConfigFromEnv } from 'antaeus.keycloak.react';
|
|
482
|
-
|
|
483
|
-
const config = {
|
|
484
|
-
...getConfigFromEnv(),
|
|
485
|
-
// Override or add more config
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
<KeycloakProvider config={config}>
|
|
489
|
-
<App />
|
|
490
|
-
</KeycloakProvider>
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
## Keycloak Server Configuration
|
|
494
|
-
|
|
495
|
-
This library requires proper Keycloak server configuration. See the comprehensive guide:
|
|
496
|
-
|
|
497
|
-
📖 **[Keycloak Setup Guide](../../KEYCLOAK_SETUP_GUIDE.md)**
|
|
498
|
-
|
|
499
|
-
### Quick Keycloak Checklist
|
|
500
|
-
|
|
501
|
-
Before using this library, ensure your Keycloak server has:
|
|
502
|
-
|
|
503
|
-
- [ ] **Realm created** with appropriate settings
|
|
504
|
-
- [ ] **Public client created** for your React app with:
|
|
505
|
-
- Client authentication: `OFF` (public client)
|
|
506
|
-
- Standard flow: `ON`
|
|
507
|
-
- PKCE enabled: `S256`
|
|
508
|
-
- Valid redirect URIs configured
|
|
509
|
-
- Web origins configured
|
|
510
|
-
- [ ] **Protocol mappers configured**:
|
|
511
|
-
- Realm roles mapper (`realm_access.roles`)
|
|
512
|
-
- Audience mapper (pointing to your API)
|
|
513
|
-
- [ ] **Users and roles created**
|
|
514
|
-
- [ ] **CORS enabled** for your frontend origin
|
|
515
|
-
|
|
516
|
-
See the full guide for detailed step-by-step instructions.
|
|
517
|
-
|
|
518
|
-
---
|
|
215
|
+
- Zero-config setup
|
|
216
|
+
- Automatic token refresh on app startup
|
|
217
|
+
- Configuration presets
|
|
218
|
+
- Enhanced token lifecycle management
|
|
219
|
+
- Auto-included SessionMonitor
|
|
220
|
+
- Built-in error notifications
|
|
519
221
|
|
|
520
222
|
## Troubleshooting
|
|
521
223
|
|
|
522
|
-
###
|
|
523
|
-
|
|
524
|
-
**Cause:** The redirect URL doesn't match Keycloak's allowed URIs.
|
|
525
|
-
|
|
526
|
-
**Solution:**
|
|
527
|
-
1. Check client configuration in Keycloak
|
|
528
|
-
2. Ensure `redirectUri` in your config matches exactly
|
|
529
|
-
3. Add `http://localhost:5173/*` to Valid Redirect URIs in Keycloak
|
|
530
|
-
|
|
531
|
-
### Roles Not Showing Up
|
|
532
|
-
|
|
533
|
-
**Cause:** Missing protocol mapper in Keycloak.
|
|
534
|
-
|
|
535
|
-
**Solution:**
|
|
536
|
-
1. Add "realm roles mapper" to your client
|
|
537
|
-
2. Configure: Token Claim Name = `realm_access.roles`
|
|
538
|
-
3. Enable: Add to access token, ID token
|
|
539
|
-
|
|
540
|
-
### CORS Errors
|
|
541
|
-
|
|
542
|
-
**Cause:** Keycloak not allowing requests from your origin.
|
|
543
|
-
|
|
544
|
-
**Solution:**
|
|
545
|
-
1. In Keycloak client settings → Web Origins
|
|
546
|
-
2. Add `http://localhost:5173` (or your origin)
|
|
547
|
-
3. Or use `*` for development only
|
|
548
|
-
|
|
549
|
-
### Token Expired Immediately
|
|
550
|
-
|
|
551
|
-
**Cause:** Clock skew or short token lifespan.
|
|
552
|
-
|
|
553
|
-
**Solution:**
|
|
554
|
-
1. Check Keycloak Realm Settings → Tokens
|
|
555
|
-
2. Increase "Access Token Lifespan" (default: 5 min)
|
|
556
|
-
3. Enable automatic token refresh:
|
|
557
|
-
```tsx
|
|
558
|
-
features: { autoRefresh: true }
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
### Device Flow Not Working
|
|
562
|
-
|
|
563
|
-
**Cause:** Device flow not enabled on Keycloak client.
|
|
564
|
-
|
|
565
|
-
**Solution:**
|
|
566
|
-
1. In client capabilities, enable: "OAuth 2.0 Device Authorization Grant"
|
|
567
|
-
2. Set device code lifespan and polling interval
|
|
568
|
-
3. Ensure backend API has device flow endpoints configured
|
|
569
|
-
|
|
570
|
-
For more troubleshooting, see [Keycloak Setup Guide - Troubleshooting](../../KEYCLOAK_SETUP_GUIDE.md#troubleshooting).
|
|
571
|
-
|
|
572
|
-
---
|
|
573
|
-
|
|
574
|
-
## Security Best Practices
|
|
575
|
-
|
|
576
|
-
### 1. Always Use HTTPS in Production
|
|
224
|
+
### 401 errors after app reopens?
|
|
577
225
|
|
|
578
226
|
```tsx
|
|
579
|
-
|
|
580
|
-
config={{
|
|
581
|
-
authority: 'https://keycloak.example.com/realms/myapp', // ← HTTPS!
|
|
582
|
-
// Never use http:// in production
|
|
583
|
-
}}
|
|
584
|
-
>
|
|
227
|
+
config={{ silentRenew: { refreshStrategy: 'both' } }}
|
|
585
228
|
```
|
|
586
229
|
|
|
587
|
-
###
|
|
588
|
-
|
|
589
|
-
The library uses `oidc-client-ts` which stores tokens securely in sessionStorage by default. Never manually store tokens in localStorage as it's vulnerable to XSS attacks.
|
|
230
|
+
### Silent renew not working?
|
|
590
231
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
Add to your `index.html`:
|
|
594
|
-
```html
|
|
595
|
-
<meta http-equiv="Content-Security-Policy"
|
|
596
|
-
content="default-src 'self'; connect-src 'self' https://your-keycloak.com;">
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### 4. Validate Tokens on the Backend
|
|
600
|
-
|
|
601
|
-
Frontend authentication is for UX only. Always validate tokens on your backend API:
|
|
602
|
-
```csharp
|
|
603
|
-
// In your .NET API
|
|
604
|
-
builder.Services.AddKeycloakAuthentication(config);
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
### 5. Use Short-Lived Access Tokens
|
|
608
|
-
|
|
609
|
-
Configure in Keycloak:
|
|
610
|
-
- Access Token Lifespan: 5 minutes
|
|
611
|
-
- Refresh Token: 30 days
|
|
612
|
-
- Enable automatic refresh in React
|
|
613
|
-
|
|
614
|
-
### 6. Implement Logout Everywhere
|
|
232
|
+
Make sure offline_access scope is included:
|
|
615
233
|
|
|
616
234
|
```tsx
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
// This logs out from Keycloak AND your app
|
|
620
|
-
await signoutRedirect();
|
|
235
|
+
config={{ scope: 'openid profile email offline_access' }}
|
|
621
236
|
```
|
|
622
237
|
|
|
623
|
-
For complete security guidelines, see [Keycloak Setup Guide - Security](../../KEYCLOAK_SETUP_GUIDE.md#security-best-practices).
|
|
624
|
-
|
|
625
|
-
---
|
|
626
|
-
|
|
627
|
-
## Requirements
|
|
628
|
-
|
|
629
|
-
- React 18+
|
|
630
|
-
- react-router-dom 6+ (for ProtectedRoute)
|
|
631
|
-
- Modern browser with ES2020 support
|
|
632
|
-
|
|
633
|
-
## Related Documentation
|
|
634
|
-
|
|
635
|
-
- 📖 [Keycloak Setup Guide](../../KEYCLOAK_SETUP_GUIDE.md) - Complete Keycloak configuration guide
|
|
636
|
-
- 📖 [Antaeus.Keycloak.AspNetCore](../keycloak-aspnetcore/README.md) - Backend API library
|
|
637
|
-
- 📖 [Quick Start](../../QUICKSTART.md) - Get started quickly
|
|
638
|
-
- 📖 [Deployment Guide](../../DEPLOYMENT.md) - Production deployment
|
|
639
|
-
|
|
640
238
|
## License
|
|
641
239
|
|
|
642
|
-
MIT
|
|
643
|
-
|
|
644
|
-
## Support
|
|
645
|
-
|
|
646
|
-
For issues and feature requests, please visit [GitHub Issues](https://github.com/sso/keycloak-react/issues).
|
|
647
|
-
|
|
648
|
-
### Useful Resources
|
|
240
|
+
MIT � Saeed Aghdam
|
|
649
241
|
|
|
650
|
-
- [Keycloak Official Documentation](https://www.keycloak.org/docs/latest/)
|
|
651
|
-
- [OAuth 2.0 / OpenID Connect Explained](https://www.oauth.com/)
|
|
652
|
-
- [oidc-client-ts Documentation](https://github.com/authts/oidc-client-ts)
|