keycloak-api-manager 5.0.5 → 5.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/docs/api/configuration.md +94 -6
- package/package.json +1 -1
|
@@ -382,16 +382,104 @@ await KeycloakManager.loginPKCE(credentials)
|
|
|
382
382
|
|
|
383
383
|
**Promise\<Object\>** - Token payload returned by Keycloak (`access_token`, `refresh_token`, `id_token`, `expires_in`, ...)
|
|
384
384
|
|
|
385
|
-
### Example
|
|
385
|
+
### Complete End-to-End Example (Express)
|
|
386
386
|
|
|
387
387
|
```javascript
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
388
|
+
const crypto = require('crypto');
|
|
389
|
+
const express = require('express');
|
|
390
|
+
const KeycloakManager = require('keycloak-api-manager');
|
|
391
|
+
|
|
392
|
+
const app = express();
|
|
393
|
+
|
|
394
|
+
const KEYCLOAK_BASE_URL = 'https://keycloak.example.com';
|
|
395
|
+
const REALM = 'my-realm';
|
|
396
|
+
const CLIENT_ID = 'my-confidential-client';
|
|
397
|
+
const CLIENT_SECRET = process.env.KC_CLIENT_SECRET;
|
|
398
|
+
const REDIRECT_URI = 'https://my-app.example.com/auth/callback';
|
|
399
|
+
|
|
400
|
+
// Demo in-memory store. In production, use Redis/session storage.
|
|
401
|
+
const pkceStore = new Map();
|
|
402
|
+
|
|
403
|
+
function base64url(buffer) {
|
|
404
|
+
return buffer
|
|
405
|
+
.toString('base64')
|
|
406
|
+
.replace(/\+/g, '-')
|
|
407
|
+
.replace(/\//g, '_')
|
|
408
|
+
.replace(/=+$/, '');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function createPkcePair() {
|
|
412
|
+
const codeVerifier = base64url(crypto.randomBytes(32));
|
|
413
|
+
const codeChallenge = base64url(
|
|
414
|
+
crypto.createHash('sha256').update(codeVerifier).digest()
|
|
415
|
+
);
|
|
416
|
+
const state = base64url(crypto.randomBytes(16));
|
|
417
|
+
return { codeVerifier, codeChallenge, state };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function bootstrap() {
|
|
421
|
+
// Configure runtime context so loginPKCE can reuse baseUrl/realm/client credentials
|
|
422
|
+
await KeycloakManager.configure({
|
|
423
|
+
baseUrl: KEYCLOAK_BASE_URL,
|
|
424
|
+
realmName: REALM,
|
|
425
|
+
grantType: 'client_credentials',
|
|
426
|
+
clientId: CLIENT_ID,
|
|
427
|
+
clientSecret: CLIENT_SECRET
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 1) Start login flow: generate PKCE and redirect to Keycloak /auth endpoint
|
|
432
|
+
app.get('/auth/login', (req, res) => {
|
|
433
|
+
const { codeVerifier, codeChallenge, state } = createPkcePair();
|
|
434
|
+
pkceStore.set(state, codeVerifier);
|
|
435
|
+
|
|
436
|
+
const authorizeUrl =
|
|
437
|
+
`${KEYCLOAK_BASE_URL}/realms/${REALM}/protocol/openid-connect/auth` +
|
|
438
|
+
`?client_id=${encodeURIComponent(CLIENT_ID)}` +
|
|
439
|
+
`&response_type=code` +
|
|
440
|
+
`&scope=${encodeURIComponent('openid profile email')}` +
|
|
441
|
+
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
|
|
442
|
+
`&code_challenge=${encodeURIComponent(codeChallenge)}` +
|
|
443
|
+
`&code_challenge_method=S256` +
|
|
444
|
+
`&state=${encodeURIComponent(state)}`;
|
|
445
|
+
|
|
446
|
+
res.redirect(authorizeUrl);
|
|
392
447
|
});
|
|
393
448
|
|
|
394
|
-
|
|
449
|
+
// 2) Callback: receive code+state, recover verifier, exchange code for tokens
|
|
450
|
+
app.get('/auth/callback', async (req, res) => {
|
|
451
|
+
const { code, state } = req.query;
|
|
452
|
+
const codeVerifier = pkceStore.get(state);
|
|
453
|
+
pkceStore.delete(state);
|
|
454
|
+
|
|
455
|
+
if (!code || !state || !codeVerifier) {
|
|
456
|
+
return res.status(400).json({ error: 'Invalid PKCE callback parameters' });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
const tokenResponse = await KeycloakManager.loginPKCE({
|
|
461
|
+
code,
|
|
462
|
+
redirectUri: REDIRECT_URI,
|
|
463
|
+
codeVerifier
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Usually you create your own app session/JWT here, instead of returning raw tokens
|
|
467
|
+
return res.json({
|
|
468
|
+
access_token: tokenResponse.access_token,
|
|
469
|
+
refresh_token: tokenResponse.refresh_token,
|
|
470
|
+
expires_in: tokenResponse.expires_in
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return res.status(401).json({ error: error.message });
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
bootstrap()
|
|
478
|
+
.then(() => app.listen(3000, () => console.log('Listening on :3000')))
|
|
479
|
+
.catch((error) => {
|
|
480
|
+
console.error('Startup error:', error.message);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
});
|
|
395
483
|
```
|
|
396
484
|
|
|
397
485
|
### Notes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keycloak-api-manager",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
4
4
|
"description": "Enhanced Node.js wrapper for Keycloak Admin REST API. Professional alternative to @keycloak/keycloak-admin-client with advanced features, bug fixes, automatic token refresh, Organizations API support, fine-grained permissions, and comprehensive resource management. Battle-tested with 113+ integration tests.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|