keycloak-api-manager 6.0.1 → 6.0.2
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/Handlers/clientPoliciesHandler.js +4 -2
- package/Handlers/clientsHandler.js +1 -13
- package/Handlers/organizationsHandler.js +2 -1
- package/Handlers/realmsHandler.js +0 -1
- package/Handlers/userProfileHandler.js +2 -2
- package/OIDC_MIGRATION_PLAN.md +5 -15
- package/README.md +4 -4
- package/docs/api/configuration.md +39 -386
- package/docs/api-reference.md +7 -7
- package/docs/guides/PKCE-Login-Flow.md +13 -659
- package/index.js +131 -0
- package/package.json +1 -1
- package/test/helpers/config.js +15 -9
|
@@ -7,10 +7,7 @@ Core API methods for initializing and managing the Keycloak Admin Client connect
|
|
|
7
7
|
- [configure()](#configure)
|
|
8
8
|
- [setConfig()](#setconfig)
|
|
9
9
|
- [getToken()](#gettoken)
|
|
10
|
-
- [
|
|
11
|
-
- [generateAuthorizationUrl()](#generateauthorizationurl)
|
|
12
|
-
- [loginPKCE()](#loginpkce)
|
|
13
|
-
- [auth()](#auth)
|
|
10
|
+
- [Deprecated OIDC Methods](#deprecated-oidc-methods)
|
|
14
11
|
- [stop()](#stop)
|
|
15
12
|
|
|
16
13
|
---
|
|
@@ -39,7 +36,7 @@ Authentication configuration object.
|
|
|
39
36
|
| `username` | string | 📋 Conditional | Required for `password` grant type |
|
|
40
37
|
| `password` | string | 📋 Conditional | Required for `password` grant type |
|
|
41
38
|
| `clientSecret` | string | 📋 Conditional | Required for `client_credentials` grant type |
|
|
42
|
-
| `tokenLifeSpan` | number | 📋 Optional | Token
|
|
39
|
+
| `tokenLifeSpan` | number | 📋 Optional | Token lifespan hint in seconds used to schedule refresh (`tokenLifeSpan / 2`) |
|
|
43
40
|
| `scope` | string | 📋 Optional | OAuth2 scope (e.g., `'offline_access'` for refresh tokens) |
|
|
44
41
|
|
|
45
42
|
### Returns
|
|
@@ -53,6 +50,7 @@ Authentication configuration object.
|
|
|
53
50
|
```javascript
|
|
54
51
|
const KeycloakManager = require('keycloak-api-manager');
|
|
55
52
|
|
|
53
|
+
// Bootstrap admin session once at startup.
|
|
56
54
|
await KeycloakManager.configure({
|
|
57
55
|
baseUrl: 'https://keycloak.example.com:8443',
|
|
58
56
|
realmName: 'master',
|
|
@@ -63,6 +61,7 @@ await KeycloakManager.configure({
|
|
|
63
61
|
tokenLifeSpan: 60
|
|
64
62
|
});
|
|
65
63
|
|
|
64
|
+
// All handler calls below reuse the authenticated admin context.
|
|
66
65
|
console.log('Authenticated successfully');
|
|
67
66
|
```
|
|
68
67
|
|
|
@@ -96,7 +95,10 @@ await KeycloakManager.configure({
|
|
|
96
95
|
|
|
97
96
|
### Automatic Token Refresh
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
The client automatically refreshes the access token using an internal timer.
|
|
99
|
+
|
|
100
|
+
- If `tokenLifeSpan` is provided and valid, refresh runs at half that interval (`tokenLifeSpan / 2`).
|
|
101
|
+
- If `tokenLifeSpan` is omitted/invalid, a safe fallback interval of 30 seconds is used.
|
|
100
102
|
|
|
101
103
|
### Error Handling
|
|
102
104
|
|
|
@@ -139,7 +141,8 @@ Configuration overrides object.
|
|
|
139
141
|
| Property | Type | Required | Description |
|
|
140
142
|
|----------|------|----------|-------------|
|
|
141
143
|
| `realmName` | string | 📋 Optional | Switch to a different realm context |
|
|
142
|
-
| `
|
|
144
|
+
| `baseUrl` | string | 📋 Optional | Override Keycloak base URL for subsequent calls |
|
|
145
|
+
| `requestConfig` | object | 📋 Optional | HTTP client request overrides forwarded to `@keycloak/keycloak-admin-client` internals (typically Axios). Supported keys depend on the installed admin client version |
|
|
143
146
|
|
|
144
147
|
### Returns
|
|
145
148
|
|
|
@@ -170,6 +173,7 @@ const users = await KeycloakManager.users.find({ max: 100 });
|
|
|
170
173
|
#### Custom Request Configuration
|
|
171
174
|
|
|
172
175
|
```javascript
|
|
176
|
+
// Runtime realm switch is immediate and affects subsequent API calls.
|
|
173
177
|
KeycloakManager.setConfig({
|
|
174
178
|
realmName: 'my-realm',
|
|
175
179
|
requestConfig: {
|
|
@@ -185,6 +189,7 @@ KeycloakManager.setConfig({
|
|
|
185
189
|
|
|
186
190
|
- `setConfig()` does NOT perform token/login calls. It only updates the runtime context.
|
|
187
191
|
- Changing `realmName` affects all subsequent API calls until changed again.
|
|
192
|
+
- Changing `baseUrl` updates the runtime target URL and is normalized (trailing slash removed).
|
|
188
193
|
- The access token remains valid across realm switches (as long as the authenticated user/service account has permissions).
|
|
189
194
|
|
|
190
195
|
---
|
|
@@ -195,7 +200,7 @@ Retrieve the current access token. Useful for debugging or passing the token to
|
|
|
195
200
|
|
|
196
201
|
**Syntax:**
|
|
197
202
|
```javascript
|
|
198
|
-
const
|
|
203
|
+
const { accessToken, refreshToken } = KeycloakManager.getToken()
|
|
199
204
|
```
|
|
200
205
|
|
|
201
206
|
### Parameters
|
|
@@ -204,7 +209,7 @@ None
|
|
|
204
209
|
|
|
205
210
|
### Returns
|
|
206
211
|
|
|
207
|
-
**
|
|
212
|
+
**{ accessToken: string, refreshToken: string }** - Current token pair managed by the client
|
|
208
213
|
|
|
209
214
|
### Examples
|
|
210
215
|
|
|
@@ -218,14 +223,15 @@ await KeycloakManager.configure({
|
|
|
218
223
|
clientId: 'admin-cli'
|
|
219
224
|
});
|
|
220
225
|
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
// Reads token currently managed by the internal refresh loop.
|
|
227
|
+
const { accessToken } = KeycloakManager.getToken();
|
|
228
|
+
console.log('Access Token:', accessToken);
|
|
223
229
|
|
|
224
230
|
// Use token with custom HTTP client
|
|
225
231
|
const axios = require('axios');
|
|
226
232
|
const response = await axios.get('https://keycloak.example.com/admin/realms/master', {
|
|
227
233
|
headers: {
|
|
228
|
-
'Authorization': `Bearer ${
|
|
234
|
+
'Authorization': `Bearer ${accessToken}`
|
|
229
235
|
}
|
|
230
236
|
});
|
|
231
237
|
```
|
|
@@ -233,382 +239,28 @@ const response = await axios.get('https://keycloak.example.com/admin/realms/mast
|
|
|
233
239
|
### Notes
|
|
234
240
|
|
|
235
241
|
- The returned token is automatically refreshed by the internal timer (if `tokenLifeSpan` was configured).
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## auth()
|
|
241
|
-
|
|
242
|
-
⚠️ **DEPRECATED (v6.0.0)** - Use [`keycloak-express-middleware.login()`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead.
|
|
243
|
-
|
|
244
|
-
This method will be removed in v7.0.0. For user authentication flows, use `keycloak-express-middleware` v6.1.0+.
|
|
245
|
-
|
|
246
|
-
See: [Migration Guide](../guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended)
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
**Legacy Note:** Backward-compatible alias of `login()`. Use `login()` instead (also deprecated).
|
|
251
|
-
|
|
252
|
-
**Syntax:**
|
|
253
|
-
```javascript
|
|
254
|
-
await KeycloakManager.auth(credentials)
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Returns
|
|
258
|
-
|
|
259
|
-
**Promise\<Object\>** - Same response as `login()`
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## login()
|
|
264
|
-
|
|
265
|
-
⚠️ **DEPRECATED (v6.0.0)** - Use [`keycloak-express-middleware.login()`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead.
|
|
266
|
-
|
|
267
|
-
This method will be removed in v7.0.0. For user authentication flows, use `keycloak-express-middleware` v6.1.0+.
|
|
268
|
-
|
|
269
|
-
See: [Migration Guide](../guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended)
|
|
242
|
+
- `accessToken` is JWT (JSON Web Token). `refreshToken` may be undefined depending on grant/scope.
|
|
270
243
|
|
|
271
244
|
---
|
|
272
245
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
This method is intended for application-level login/token flows (for users, service clients, or third-party integrations) using this package as a wrapper.
|
|
276
|
-
|
|
277
|
-
It does **not** reconfigure or replace the internal admin session created by `configure()`.
|
|
278
|
-
|
|
279
|
-
**Syntax:**
|
|
280
|
-
```javascript
|
|
281
|
-
await KeycloakManager.login(credentials)
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Parameters
|
|
285
|
-
|
|
286
|
-
#### credentials (Object) ⚠️ Required
|
|
287
|
-
|
|
288
|
-
Form parameters sent to `/protocol/openid-connect/token`.
|
|
289
|
-
|
|
290
|
-
Common fields:
|
|
246
|
+
## Deprecated OIDC Methods
|
|
291
247
|
|
|
292
|
-
|
|
293
|
-
|----------|------|----------|-------------|
|
|
294
|
-
| `grant_type` | string | ⚠️ Yes | OAuth2 grant type (`password`, `client_credentials`, `refresh_token`, `authorization_code`) |
|
|
295
|
-
| `username` | string | 📋 Conditional | Required for `password` grant |
|
|
296
|
-
| `password` | string | 📋 Conditional | Required for `password` grant |
|
|
297
|
-
| `client_id` | string | 📋 Optional | If omitted, runtime `clientId` from `configure()` is used |
|
|
298
|
-
| `client_secret` | string | 📋 Optional | If omitted, runtime `clientSecret` from `configure()` is used |
|
|
299
|
-
| `refresh_token` | string | 📋 Conditional | Required for `refresh_token` grant |
|
|
300
|
-
| `scope` | string | 📋 Optional | OAuth scopes (e.g. `openid profile email offline_access`) |
|
|
301
|
-
| `code` | string | 📋 Conditional | Required for `authorization_code` grant |
|
|
302
|
-
| `redirect_uri` | string | 📋 Conditional | Required for `authorization_code` grant |
|
|
248
|
+
The following methods are still available only for backward compatibility and are deprecated since v6.0.0:
|
|
303
249
|
|
|
304
|
-
|
|
250
|
+
- `auth(credentials)`
|
|
251
|
+
- `login(credentials)`
|
|
252
|
+
- `generateAuthorizationUrl(options)`
|
|
253
|
+
- `loginPKCE(credentials)`
|
|
305
254
|
|
|
306
|
-
|
|
255
|
+
These methods will be removed in v7.0.0.
|
|
307
256
|
|
|
308
|
-
|
|
257
|
+
For all user authentication flows (including Authorization Code + PKCE), use keycloak-express-middleware:
|
|
309
258
|
|
|
310
|
-
|
|
259
|
+
- https://github.com/smartenv-crs4/keycloak-express-middleware
|
|
311
260
|
|
|
312
|
-
|
|
313
|
-
// Admin setup for wrapper/handlers
|
|
314
|
-
await KeycloakManager.configure({
|
|
315
|
-
baseUrl: 'https://keycloak.example.com',
|
|
316
|
-
realmName: 'master',
|
|
317
|
-
username: 'admin',
|
|
318
|
-
password: 'admin',
|
|
319
|
-
grantType: 'password',
|
|
320
|
-
clientId: 'admin-cli'
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Application login/token request for an end-user
|
|
324
|
-
const tokenResponse = await KeycloakManager.login({
|
|
325
|
-
grant_type: 'password',
|
|
326
|
-
username: 'end-user',
|
|
327
|
-
password: 'user-password',
|
|
328
|
-
scope: 'openid profile email'
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
console.log(tokenResponse.access_token);
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
#### Client Credentials Login
|
|
335
|
-
|
|
336
|
-
```javascript
|
|
337
|
-
const tokenResponse = await KeycloakManager.login({
|
|
338
|
-
grant_type: 'client_credentials',
|
|
339
|
-
client_id: 'my-public-api',
|
|
340
|
-
client_secret: process.env.API_CLIENT_SECRET
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
console.log(tokenResponse.access_token);
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
#### Refresh Token Flow
|
|
347
|
-
|
|
348
|
-
```javascript
|
|
349
|
-
const refreshed = await KeycloakManager.login({
|
|
350
|
-
grant_type: 'refresh_token',
|
|
351
|
-
refresh_token: oldRefreshToken
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
console.log(refreshed.access_token);
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Notes
|
|
358
|
-
|
|
359
|
-
- `login()` posts to `${baseUrl}/realms/${realmName}/protocol/openid-connect/token`.
|
|
360
|
-
- `login()` returns raw token endpoint payload and throws on non-2xx responses.
|
|
361
|
-
- `login()`/`auth()` do not change handler wiring, runtime config, or the internal admin refresh timer.
|
|
362
|
-
- Runtime `clientId`/`clientSecret` are appended automatically if configured and not overridden in request payload.
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## generateAuthorizationUrl()
|
|
367
|
-
|
|
368
|
-
⚠️ **DEPRECATED (v6.0.0)** - Use [`keycloak-express-middleware.generateAuthorizationUrl()`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead.
|
|
369
|
-
|
|
370
|
-
This method will be removed in v7.0.0. For PKCE authentication flows, use `keycloak-express-middleware` v6.1.0+.
|
|
371
|
-
|
|
372
|
-
See: [Migration Guide](../guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended)
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
Generate OAuth2 Authorization Code + PKCE flow initialization. Returns a ready-to-use authorization URL and PKCE pair for server-side session storage.
|
|
377
|
-
|
|
378
|
-
This helper simplifies the first step of PKCE login: generating the authorization URL with PKCE challenge and state parameter.
|
|
379
|
-
|
|
380
|
-
**Syntax:**
|
|
381
|
-
```javascript
|
|
382
|
-
const pkceFlow = KeycloakManager.generateAuthorizationUrl(options)
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### Parameters
|
|
386
|
-
|
|
387
|
-
#### options (Object) ⚠️ Required
|
|
388
|
-
|
|
389
|
-
| Property | Type | Required | Description |
|
|
390
|
-
|----------|------|----------|-------------|
|
|
391
|
-
| `redirect_uri` | string | ⚠️ Yes* | Redirect URI where user returns after login |
|
|
392
|
-
| `redirectUri` | string | ⚠️ Yes* | CamelCase alias of `redirect_uri` |
|
|
393
|
-
| `scope` | string | 📋 Optional | Space-separated scopes (default: `'openid profile email'`) |
|
|
394
|
-
| `state` | string | 📋 Optional | Custom state value (auto-generated if not provided) |
|
|
395
|
-
|
|
396
|
-
`*` required with either snake_case or camelCase form.
|
|
397
|
-
|
|
398
|
-
### Returns
|
|
399
|
-
|
|
400
|
-
**Object** - PKCE flow initialization data:
|
|
401
|
-
|
|
402
|
-
```javascript
|
|
403
|
-
{
|
|
404
|
-
authUrl: 'https://keycloak.example.com/realms/my-realm/protocol/openid-connect/auth?...',
|
|
405
|
-
state: 'random_state_string',
|
|
406
|
-
codeVerifier: 'random_code_verifier_string'
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
- `authUrl`: Ready-to-use authorization URL to redirect user to
|
|
411
|
-
- `state`: CSRF token to validate in callback (store in session)
|
|
412
|
-
- `codeVerifier`: PKCE proof to exchange for code in callback (store in session, **never expose to client**)
|
|
413
|
-
|
|
414
|
-
### Example
|
|
415
|
-
|
|
416
|
-
```javascript
|
|
417
|
-
// Step 1: Generate authorization URL
|
|
418
|
-
app.get('/auth/login', (req, res) => {
|
|
419
|
-
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
420
|
-
redirect_uri: `${process.env.APP_URL}/auth/callback`,
|
|
421
|
-
scope: 'openid profile email'
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// Store in session server-side
|
|
425
|
-
req.session.pkce_state = pkceFlow.state;
|
|
426
|
-
req.session.pkce_verifier = pkceFlow.codeVerifier;
|
|
427
|
-
|
|
428
|
-
// Redirect user to Keycloak
|
|
429
|
-
res.redirect(pkceFlow.authUrl);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Step 2: In callback, recover verifier and exchange code
|
|
433
|
-
app.get('/auth/callback', async (req, res) => {
|
|
434
|
-
const { code, state } = req.query;
|
|
435
|
-
|
|
436
|
-
// Validate state
|
|
437
|
-
if (state !== req.session.pkce_state) {
|
|
438
|
-
return res.status(400).send('CSRF attack detected');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Exchange code for token
|
|
442
|
-
const tokens = await KeycloakManager.loginPKCE({
|
|
443
|
-
code,
|
|
444
|
-
redirect_uri: `${process.env.APP_URL}/auth/callback`,
|
|
445
|
-
code_verifier: req.session.pkce_verifier
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Use tokens...
|
|
449
|
-
});
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Notes
|
|
453
|
-
|
|
454
|
-
- `generateAuthorizationUrl()` does **not** call Keycloak; it generates URLs and PKCE values locally.
|
|
455
|
-
- `state` and `codeVerifier` must be stored **server-side only** in session storage, never in cookies or local storage.
|
|
456
|
-
- `codeVerifier` must be kept secret and never exposed to the browser.
|
|
457
|
-
- `state` provides CSRF protection and must be validated in the callback.
|
|
458
|
-
- Uses cryptographically secure random generation for `codeVerifier` and `state`.
|
|
459
|
-
- Code challenge is SHA256-hashed (S256 method), not plain text.
|
|
460
|
-
|
|
461
|
-
---
|
|
462
|
-
|
|
463
|
-
## loginPKCE()
|
|
464
|
-
|
|
465
|
-
⚠️ **DEPRECATED (v6.0.0)** - Use [`keycloak-express-middleware.loginPKCE()`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead.
|
|
466
|
-
|
|
467
|
-
This method will be removed in v7.0.0. For PKCE authentication flows, use `keycloak-express-middleware` v6.1.0+.
|
|
468
|
-
|
|
469
|
-
See: [Migration Guide](../guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended)
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
Perform Authorization Code + PKCE token exchange.
|
|
474
|
-
|
|
475
|
-
This helper is intended for the callback step after user login on Keycloak, where your backend receives an authorization `code` and exchanges it with `code_verifier`.
|
|
476
|
-
|
|
477
|
-
> **📖 For a complete step-by-step guide on implementing PKCE flow in your application, see [PKCE Login Flow Guide](../guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended)**
|
|
478
|
-
|
|
479
|
-
**Syntax:**
|
|
480
|
-
```javascript
|
|
481
|
-
await KeycloakManager.loginPKCE(credentials)
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
### Parameters
|
|
485
|
-
|
|
486
|
-
#### credentials (Object) ⚠️ Required
|
|
487
|
-
|
|
488
|
-
| Property | Type | Required | Description |
|
|
489
|
-
|----------|------|----------|-------------|
|
|
490
|
-
| `code` | string | ⚠️ Yes | Authorization code returned by Keycloak |
|
|
491
|
-
| `redirect_uri` | string | ⚠️ Yes* | Redirect URI used in authorize request |
|
|
492
|
-
| `redirectUri` | string | ⚠️ Yes* | CamelCase alias of `redirect_uri` |
|
|
493
|
-
| `code_verifier` | string | ⚠️ Yes* | PKCE code verifier |
|
|
494
|
-
| `codeVerifier` | string | ⚠️ Yes* | CamelCase alias of `code_verifier` |
|
|
495
|
-
| `client_id` | string | 📋 Optional | Overrides runtime `clientId` |
|
|
496
|
-
| `clientId` | string | 📋 Optional | CamelCase alias of `client_id` |
|
|
497
|
-
| `client_secret` | string | 📋 Optional | Overrides runtime `clientSecret` |
|
|
498
|
-
| `clientSecret` | string | 📋 Optional | CamelCase alias of `client_secret` |
|
|
499
|
-
| `scope` | string | 📋 Optional | Additional scope string |
|
|
500
|
-
|
|
501
|
-
`*` required with either snake_case or camelCase form.
|
|
502
|
-
|
|
503
|
-
### Returns
|
|
504
|
-
|
|
505
|
-
**Promise\<Object\>** - Token payload returned by Keycloak (`access_token`, `refresh_token`, `id_token`, `expires_in`, ...)
|
|
506
|
-
|
|
507
|
-
### Complete End-to-End Example (Express)
|
|
508
|
-
|
|
509
|
-
```javascript
|
|
510
|
-
const crypto = require('crypto');
|
|
511
|
-
const express = require('express');
|
|
512
|
-
const KeycloakManager = require('keycloak-api-manager');
|
|
513
|
-
|
|
514
|
-
const app = express();
|
|
515
|
-
|
|
516
|
-
const KEYCLOAK_BASE_URL = 'https://keycloak.example.com';
|
|
517
|
-
const REALM = 'my-realm';
|
|
518
|
-
const CLIENT_ID = 'my-confidential-client';
|
|
519
|
-
const CLIENT_SECRET = process.env.KC_CLIENT_SECRET;
|
|
520
|
-
const REDIRECT_URI = 'https://my-app.example.com/auth/callback';
|
|
521
|
-
|
|
522
|
-
// Demo in-memory store. In production, use Redis/session storage.
|
|
523
|
-
const pkceStore = new Map();
|
|
524
|
-
|
|
525
|
-
function base64url(buffer) {
|
|
526
|
-
return buffer
|
|
527
|
-
.toString('base64')
|
|
528
|
-
.replace(/\+/g, '-')
|
|
529
|
-
.replace(/\//g, '_')
|
|
530
|
-
.replace(/=+$/, '');
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function createPkcePair() {
|
|
534
|
-
const codeVerifier = base64url(crypto.randomBytes(32));
|
|
535
|
-
const codeChallenge = base64url(
|
|
536
|
-
crypto.createHash('sha256').update(codeVerifier).digest()
|
|
537
|
-
);
|
|
538
|
-
const state = base64url(crypto.randomBytes(16));
|
|
539
|
-
return { codeVerifier, codeChallenge, state };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
async function bootstrap() {
|
|
543
|
-
// Configure runtime context so loginPKCE can reuse baseUrl/realm/client credentials
|
|
544
|
-
await KeycloakManager.configure({
|
|
545
|
-
baseUrl: KEYCLOAK_BASE_URL,
|
|
546
|
-
realmName: REALM,
|
|
547
|
-
grantType: 'client_credentials',
|
|
548
|
-
clientId: CLIENT_ID,
|
|
549
|
-
clientSecret: CLIENT_SECRET
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// 1) Start login flow: generate PKCE and redirect to Keycloak /auth endpoint
|
|
554
|
-
app.get('/auth/login', (req, res) => {
|
|
555
|
-
const { codeVerifier, codeChallenge, state } = createPkcePair();
|
|
556
|
-
pkceStore.set(state, codeVerifier);
|
|
557
|
-
|
|
558
|
-
const authorizeUrl =
|
|
559
|
-
`${KEYCLOAK_BASE_URL}/realms/${REALM}/protocol/openid-connect/auth` +
|
|
560
|
-
`?client_id=${encodeURIComponent(CLIENT_ID)}` +
|
|
561
|
-
`&response_type=code` +
|
|
562
|
-
`&scope=${encodeURIComponent('openid profile email')}` +
|
|
563
|
-
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
|
|
564
|
-
`&code_challenge=${encodeURIComponent(codeChallenge)}` +
|
|
565
|
-
`&code_challenge_method=S256` +
|
|
566
|
-
`&state=${encodeURIComponent(state)}`;
|
|
567
|
-
|
|
568
|
-
res.redirect(authorizeUrl);
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
// 2) Callback: receive code+state, recover verifier, exchange code for tokens
|
|
572
|
-
app.get('/auth/callback', async (req, res) => {
|
|
573
|
-
const { code, state } = req.query;
|
|
574
|
-
const codeVerifier = pkceStore.get(state);
|
|
575
|
-
pkceStore.delete(state);
|
|
576
|
-
|
|
577
|
-
if (!code || !state || !codeVerifier) {
|
|
578
|
-
return res.status(400).json({ error: 'Invalid PKCE callback parameters' });
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
try {
|
|
582
|
-
const tokenResponse = await KeycloakManager.loginPKCE({
|
|
583
|
-
code,
|
|
584
|
-
redirectUri: REDIRECT_URI,
|
|
585
|
-
codeVerifier
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
// Usually you create your own app session/JWT here, instead of returning raw tokens
|
|
589
|
-
return res.json({
|
|
590
|
-
access_token: tokenResponse.access_token,
|
|
591
|
-
refresh_token: tokenResponse.refresh_token,
|
|
592
|
-
expires_in: tokenResponse.expires_in
|
|
593
|
-
});
|
|
594
|
-
} catch (error) {
|
|
595
|
-
return res.status(401).json({ error: error.message });
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
bootstrap()
|
|
600
|
-
.then(() => app.listen(3000, () => console.log('Listening on :3000')))
|
|
601
|
-
.catch((error) => {
|
|
602
|
-
console.error('Startup error:', error.message);
|
|
603
|
-
process.exit(1);
|
|
604
|
-
});
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
### Notes
|
|
261
|
+
Migration notes:
|
|
608
262
|
|
|
609
|
-
-
|
|
610
|
-
- If `client_id/client_secret` are omitted, runtime values from `configure()` are used.
|
|
611
|
-
- `loginPKCE()` does not generate authorize URLs, `state`, or PKCE challenge; it only performs token exchange.
|
|
263
|
+
- [OIDC Migration Plan](../../OIDC_MIGRATION_PLAN.md)
|
|
612
264
|
|
|
613
265
|
---
|
|
614
266
|
|
|
@@ -698,7 +350,7 @@ const KeycloakManager = require('keycloak-api-manager');
|
|
|
698
350
|
|
|
699
351
|
async function keycloakWorkflow() {
|
|
700
352
|
try {
|
|
701
|
-
// 1
|
|
353
|
+
// 1) Configure and authenticate once.
|
|
702
354
|
await KeycloakManager.configure({
|
|
703
355
|
baseUrl: 'https://keycloak.example.com:8443',
|
|
704
356
|
realmName: 'master',
|
|
@@ -710,27 +362,28 @@ async function keycloakWorkflow() {
|
|
|
710
362
|
});
|
|
711
363
|
console.log('✓ Authenticated');
|
|
712
364
|
|
|
713
|
-
// 2
|
|
365
|
+
// 2) Run operations in the current realm context.
|
|
714
366
|
const masterRealms = await KeycloakManager.realms.find();
|
|
715
367
|
console.log(`✓ Found ${masterRealms.length} realms`);
|
|
716
368
|
|
|
717
|
-
// 3
|
|
369
|
+
// 3) Switch realm context for following API calls.
|
|
718
370
|
KeycloakManager.setConfig({ realmName: 'my-app' });
|
|
719
371
|
console.log('✓ Switched to my-app realm');
|
|
720
372
|
|
|
721
|
-
// 4
|
|
373
|
+
// 4) Execute admin operations in the target realm.
|
|
722
374
|
const users = await KeycloakManager.users.find({ max: 100 });
|
|
723
375
|
console.log(`✓ Found ${users.length} users in my-app`);
|
|
724
376
|
|
|
725
|
-
// 5
|
|
726
|
-
const
|
|
727
|
-
console.log('✓ Current token:',
|
|
377
|
+
// 5) Optionally inspect the token for debugging/integration checks.
|
|
378
|
+
const { accessToken } = KeycloakManager.getToken();
|
|
379
|
+
console.log('✓ Current token:', accessToken.substring(0, 50) + '...');
|
|
728
380
|
|
|
729
|
-
// 6.
|
|
381
|
+
// 6) Always stop refresh timer before process exit.
|
|
730
382
|
KeycloakManager.stop();
|
|
731
383
|
console.log('✓ Stopped token refresh');
|
|
732
384
|
|
|
733
385
|
} catch (error) {
|
|
386
|
+
// Ensure timer cleanup also happens on failures.
|
|
734
387
|
console.error('✗ Error:', error.message);
|
|
735
388
|
KeycloakManager.stop();
|
|
736
389
|
process.exit(1);
|
package/docs/api-reference.md
CHANGED
|
@@ -4,8 +4,8 @@ Complete API documentation for keycloak-api-manager.
|
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
-
### Guides
|
|
8
|
-
- [
|
|
7
|
+
### Guides
|
|
8
|
+
- [OIDC Migration Plan](../OIDC_MIGRATION_PLAN.md) - Deprecation status and migration notes to keycloak-express-middleware
|
|
9
9
|
|
|
10
10
|
### Core API
|
|
11
11
|
- [Configuration & Authentication](api/configuration.md) - Setup, authentication, and lifecycle management
|
|
@@ -78,11 +78,11 @@ KeycloakManager.stop();
|
|
|
78
78
|
|-----------|-------------|--------|
|
|
79
79
|
| `configure()` | Authentication and setup | Core |
|
|
80
80
|
| `setConfig()` | Runtime configuration | Core |
|
|
81
|
-
| `getToken()` | Get current access token | Core |
|
|
82
|
-
| `login()` |
|
|
83
|
-
| `generateAuthorizationUrl()` |
|
|
84
|
-
| `loginPKCE()` |
|
|
85
|
-
| `auth()` |
|
|
81
|
+
| `getToken()` | Get current access/refresh token pair | Core |
|
|
82
|
+
| `login()` | Deprecated OIDC token endpoint wrapper (moved to keycloak-express-middleware) | Core |
|
|
83
|
+
| `generateAuthorizationUrl()` | Deprecated PKCE helper (moved to keycloak-express-middleware) | Core |
|
|
84
|
+
| `loginPKCE()` | Deprecated PKCE token exchange helper (moved to keycloak-express-middleware) | Core |
|
|
85
|
+
| `auth()` | Deprecated backward-compatible alias of `login()` | Core |
|
|
86
86
|
| `stop()` | Stop token refresh timer | Core |
|
|
87
87
|
| `realms` | Realm management | realmsHandler |
|
|
88
88
|
| `users` | User management | usersHandler |
|