keycloak-api-manager 5.0.7 → 6.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.
@@ -0,0 +1,214 @@
1
+ # OIDC Methods Migration Plan - keycloak-api-manager
2
+
3
+ ## Overview
4
+
5
+ L'architettura prevede una **migrazione pianificata** dei metodi OIDC (`auth()`, `login()`, `loginPKCE()`) da `keycloak-api-manager` a `keycloak-express-middleware`.
6
+
7
+ ## Current State (v5.0.8)
8
+
9
+ **In keycloak-api-manager:**
10
+ - ✅ `auth(credentials)` - OIDC token endpoint wrapper
11
+ - ✅ `login(credentials)` - Generic token grant helper
12
+ - ✅ `generateAuthorizationUrl(options)` - PKCE URL generator
13
+ - ✅ `loginPKCE(credentials)` - PKCE token exchange
14
+
15
+ ## Why This Migration?
16
+
17
+ ### Separation of Concerns
18
+
19
+ ```
20
+ keycloak-api-manager (Admin API Management)
21
+ ├── configure() ← Admin setup
22
+ ├── Users.find() ← Admin operations
23
+ ├── Realms.create() ← Admin operations
24
+ └── ❌ login() / auth() ← Should NOT be here!
25
+
26
+ keycloak-express-middleware (User Authentication)
27
+ ├── protectMiddleware() ← Route protection
28
+ ├── Express integration ← Session, cookies
29
+ └── ✅ generateAuthorizationUrl() ← Belongs here
30
+ └── ✅ login() ← Belongs here
31
+ └── ✅ loginPKCE() ← Belongs here
32
+ ```
33
+
34
+ ### Why Middleware is Better
35
+
36
+ 1. **Natural Context:** Express app has sessions, cookies, redirects
37
+ 2. **No Admin Context:** Users don't need admin API management
38
+ 3. **Tighter Integration:** Works with middleware ecosystem
39
+ 4. **Cleaner Namespaces:** Each package has clear responsibilities
40
+
41
+ ## Migration Timeline
42
+
43
+ ### Phase 1: ✅ DONE (Now)
44
+ - [x] Create ready-to-integrate files in middleware (`oidc-methods.js`)
45
+ - [x] Write comprehensive tests (21 tests, all passing)
46
+ - [x] Document integration guide
47
+ - [x] Commit to middleware repo
48
+
49
+ **Status:** Ready for integration into `keycloak-express-middleware`
50
+
51
+ ### Phase 2: Manual Integration (When You're Ready)
52
+ - [ ] Integrate `oidc-methods.js` into middleware `index.js`
53
+ - [ ] Run tests to verify
54
+ - [ ] Release `keycloak-express-middleware v6.1.0`
55
+
56
+ **Your Action:** Follow `keycloak-express-middleware/OIDC_INTEGRATION_GUIDE.md`
57
+
58
+ ### Phase 3: Deprecation in keycloak-api-manager (v6.0.0)
59
+
60
+ Once middleware integration is confirmed:
61
+
62
+ 1. **Mark methods as deprecated** in index.js:
63
+ ```javascript
64
+ exports.auth = async function auth(credentials = {}) {
65
+ console.warn('⚠️ DEPRECATED: auth() is deprecated in v6.0. ' +
66
+ 'Use keycloak-express-middleware.login() instead. ' +
67
+ 'See: https://...');
68
+ return requestOidcToken(credentials);
69
+ };
70
+ ```
71
+
72
+ 2. **Update documentation:**
73
+ - Add migration guide in README
74
+ - Mark OIDC methods as deprecated in API docs
75
+ - Link to middleware documentation
76
+
77
+ 3. **Release v6.0.0:**
78
+ - Update package.json version
79
+ - Add breaking change notice
80
+ - Include migration guide in release notes
81
+
82
+ 4. **Future (v7.0.0):**
83
+ - Optionally remove these methods entirely
84
+ - Keep at least 1 major version for migration
85
+
86
+ ## Current keycloak-api-manager Methods
87
+
88
+ ### `auth(credentials)` - Generic OIDC Token Grant
89
+ ```javascript
90
+ // Still works, but should use middleware
91
+ const token = await KeycloakManager.auth({
92
+ grant_type: 'password',
93
+ username: 'user',
94
+ password: 'pass'
95
+ });
96
+ ```
97
+
98
+ **Migration Path:**
99
+ ```javascript
100
+ // Instead use middleware
101
+ const token = await keycloakMiddleware.login({
102
+ grant_type: 'password',
103
+ username: 'user',
104
+ password: 'pass'
105
+ });
106
+ ```
107
+
108
+ ### `login(credentials)` - Preferred Alias
109
+ ```javascript
110
+ // Currently the preferred way in API manager
111
+ const token = await KeycloakManager.login({
112
+ grant_type: 'client_credentials'
113
+ });
114
+ ```
115
+
116
+ **Migration Path:**
117
+ ```javascript
118
+ // Move to middleware
119
+ const token = await keycloakMiddleware.login({
120
+ grant_type: 'client_credentials'
121
+ });
122
+ ```
123
+
124
+ ### `generateAuthorizationUrl(options)` - PKCE URL Generator
125
+ ```javascript
126
+ // Generates OAuth2 authorization URL + PKCE pair
127
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl({
128
+ redirect_uri: 'https://app/callback'
129
+ });
130
+ ```
131
+
132
+ **Migration Path:**
133
+ ```javascript
134
+ // Move to middleware
135
+ const pkceFlow = keycloakMiddleware.generateAuthorizationUrl({
136
+ redirect_uri: 'https://app/callback'
137
+ });
138
+ ```
139
+
140
+ ### `loginPKCE(credentials)` - PKCE Token Exchange
141
+ ```javascript
142
+ // Exchange auth code for tokens in callback
143
+ const token = await KeycloakManager.loginPKCE({
144
+ code: req.query.code,
145
+ redirect_uri: 'https://app/callback',
146
+ code_verifier: req.session.verifier
147
+ });
148
+ ```
149
+
150
+ **Migration Path:**
151
+ ```javascript
152
+ // Move to middleware
153
+ const token = await keycloakMiddleware.loginPKCE({
154
+ code: req.query.code,
155
+ redirect_uri: 'https://app/callback',
156
+ code_verifier: req.session.verifier
157
+ });
158
+ ```
159
+
160
+ ## What Stays in keycloak-api-manager
161
+
162
+ ✅ **These remain unchanged forever:**
163
+ - `configure(credentials)` - Admin authentication
164
+ - `setConfig(overrides)` - Configuration management
165
+ - `getToken()` - Get current admin token
166
+ - `stop()` - Cleanup
167
+ - **All handlers:** `users`, `realms`, `clients`, `groups`, `roles`, etc.
168
+
169
+ The package remains the **dedicated Admin API Manager** - exactly what it should be.
170
+
171
+ ## Migration Guide for Users (TBD)
172
+
173
+ When ready, we'll publish:
174
+
175
+ ```markdown
176
+ # Migrating OIDC Methods from keycloak-api-manager to keycloak-express-middleware
177
+
178
+ ## Version Support
179
+
180
+ | Method | deprecated | removed | alternative |
181
+ |--------|-----------|---------|-------------|
182
+ | `auth()` | v6.0.0 | v7.0.0 | middleware.login() |
183
+ | `login()` | v6.0.0 | v7.0.0 | middleware.login() |
184
+ | `generateAuthorizationUrl()` | v6.0.0 | v7.0.0 | middleware.generateAuthorizationUrl() |
185
+ | `loginPKCE()` | v6.0.0 | v7.0.0 | middleware.loginPKCE() |
186
+
187
+ ## Migration Steps
188
+
189
+ 1. Install/update middleware
190
+ 2. Replace method calls
191
+ 3. Update imports
192
+ 4. Test
193
+ ```
194
+
195
+ ## Next Actions
196
+
197
+ ### You Should Do:
198
+ 1. Review `keycloak-express-middleware/OIDC_INTEGRATION_GUIDE.md`
199
+ 2. Decide if you want to integrate manually or have me automate it
200
+ 3. Once middleware is ready, let me know
201
+
202
+ ### I Will Do (When You Say):
203
+ 1. Integrate methods into middleware `index.js`
204
+ 2. Release `keycloak-express-middleware v6.1.0`
205
+ 3. Deprecate in `keycloak-api-manager v6.0.0`
206
+ 4. Create migration guide
207
+ 5. Update all documentation
208
+
209
+ ## Questions?
210
+
211
+ - How should we handle the transition period?
212
+ - Do we support both packages simultaneously?
213
+ - Timeline for full migration?
214
+ - Any concerns about the approach?
package/README.md CHANGED
@@ -25,6 +25,12 @@ It provides a stable, function-oriented interface for managing Keycloak resource
25
25
  npm install keycloak-api-manager
26
26
  ```
27
27
 
28
+ > **⚠️ DEPRECATION NOTICE (v6.0.0):** The OIDC authentication methods (`login()`, `loginPKCE()`, `generateAuthorizationUrl()`, `auth()`) have been **deprecated** and moved to [`keycloak-express-middleware`](https://github.com/smartenv-crs4/keycloak-express-middleware).
29
+ >
30
+ > **This package is now exclusively for Keycloak admin resource management.** For user authentication flows, use `keycloak-express-middleware` instead.
31
+ >
32
+ > See [OIDC_MIGRATION_PLAN.md](OIDC_MIGRATION_PLAN.md) for migration details.
33
+
28
34
  ## Quick Start
29
35
 
30
36
  ```js
@@ -65,16 +71,13 @@ In Keycloak 26.x, management-permissions APIs used by group/user fine-grained te
65
71
  - `configure(credentials)`
66
72
  - `setConfig(overrides)`
67
73
  - `getToken()`
68
- - `login(credentials)`
69
- - `loginPKCE(credentials)`
70
- - `auth(credentials)`
71
74
  - `stop()`
75
+ - ~~`login(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
76
+ - ~~`generateAuthorizationUrl(options)`~~ **DEPRECATED** - moved to keycloak-express-middleware
77
+ - ~~`loginPKCE(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
78
+ - ~~`auth(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
72
79
 
73
- `login(credentials)` is the preferred OIDC token endpoint helper for login/token grant flows (user/client).
74
-
75
- `loginPKCE(credentials)` is a specialized helper for Authorization Code + PKCE token exchange.
76
-
77
- `auth(credentials)` is kept as backward-compatible alias and does not replace the internal admin session configured by `configure()`.
80
+ **Note:** OIDC authentication methods have been deprecated in v6.0.0. Use [`keycloak-express-middleware`](https://github.com/smartenv-crs4/keycloak-express-middleware) for user authentication flows.
78
81
 
79
82
  Configured handler namespaces:
80
83
 
@@ -158,10 +161,15 @@ docs/ # Centralized documentation
158
161
 
159
162
  ## Versioning and Compatibility
160
163
 
161
- - Package version: `5.0.1`
164
+ - Package version: `6.0.0`
162
165
  - Keycloak Admin client dependency: `@keycloak/keycloak-admin-client`
163
166
  - Main compatibility target: Keycloak 25/26
164
167
 
168
+ ### Breaking Changes in v6.0.0
169
+
170
+ OIDC authentication methods (`login()`, `loginPKCE()`, `generateAuthorizationUrl()`, `auth()`) are now deprecated.
171
+ These methods will be removed in v7.0.0. Migrate to `keycloak-express-middleware` for user authentication.
172
+
165
173
  ## License
166
174
 
167
175
  MIT
@@ -8,6 +8,7 @@ Core API methods for initializing and managing the Keycloak Admin Client connect
8
8
  - [setConfig()](#setconfig)
9
9
  - [getToken()](#gettoken)
10
10
  - [login()](#login)
11
+ - [generateAuthorizationUrl()](#generateauthorizationurl)
11
12
  - [loginPKCE()](#loginpkce)
12
13
  - [auth()](#auth)
13
14
  - [stop()](#stop)
@@ -348,6 +349,95 @@ console.log(refreshed.access_token);
348
349
 
349
350
  ---
350
351
 
352
+ ## generateAuthorizationUrl()
353
+
354
+ Generate OAuth2 Authorization Code + PKCE flow initialization. Returns a ready-to-use authorization URL and PKCE pair for server-side session storage.
355
+
356
+ This helper simplifies the first step of PKCE login: generating the authorization URL with PKCE challenge and state parameter.
357
+
358
+ **Syntax:**
359
+ ```javascript
360
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl(options)
361
+ ```
362
+
363
+ ### Parameters
364
+
365
+ #### options (Object) ⚠️ Required
366
+
367
+ | Property | Type | Required | Description |
368
+ |----------|------|----------|-------------|
369
+ | `redirect_uri` | string | ⚠️ Yes* | Redirect URI where user returns after login |
370
+ | `redirectUri` | string | ⚠️ Yes* | CamelCase alias of `redirect_uri` |
371
+ | `scope` | string | 📋 Optional | Space-separated scopes (default: `'openid profile email'`) |
372
+ | `state` | string | 📋 Optional | Custom state value (auto-generated if not provided) |
373
+
374
+ `*` required with either snake_case or camelCase form.
375
+
376
+ ### Returns
377
+
378
+ **Object** - PKCE flow initialization data:
379
+
380
+ ```javascript
381
+ {
382
+ authUrl: 'https://keycloak.example.com/realms/my-realm/protocol/openid-connect/auth?...',
383
+ state: 'random_state_string',
384
+ codeVerifier: 'random_code_verifier_string'
385
+ }
386
+ ```
387
+
388
+ - `authUrl`: Ready-to-use authorization URL to redirect user to
389
+ - `state`: CSRF token to validate in callback (store in session)
390
+ - `codeVerifier`: PKCE proof to exchange for code in callback (store in session, **never expose to client**)
391
+
392
+ ### Example
393
+
394
+ ```javascript
395
+ // Step 1: Generate authorization URL
396
+ app.get('/auth/login', (req, res) => {
397
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl({
398
+ redirect_uri: `${process.env.APP_URL}/auth/callback`,
399
+ scope: 'openid profile email'
400
+ });
401
+
402
+ // Store in session server-side
403
+ req.session.pkce_state = pkceFlow.state;
404
+ req.session.pkce_verifier = pkceFlow.codeVerifier;
405
+
406
+ // Redirect user to Keycloak
407
+ res.redirect(pkceFlow.authUrl);
408
+ });
409
+
410
+ // Step 2: In callback, recover verifier and exchange code
411
+ app.get('/auth/callback', async (req, res) => {
412
+ const { code, state } = req.query;
413
+
414
+ // Validate state
415
+ if (state !== req.session.pkce_state) {
416
+ return res.status(400).send('CSRF attack detected');
417
+ }
418
+
419
+ // Exchange code for token
420
+ const tokens = await KeycloakManager.loginPKCE({
421
+ code,
422
+ redirect_uri: `${process.env.APP_URL}/auth/callback`,
423
+ code_verifier: req.session.pkce_verifier
424
+ });
425
+
426
+ // Use tokens...
427
+ });
428
+ ```
429
+
430
+ ### Notes
431
+
432
+ - `generateAuthorizationUrl()` does **not** call Keycloak; it generates URLs and PKCE values locally.
433
+ - `state` and `codeVerifier` must be stored **server-side only** in session storage, never in cookies or local storage.
434
+ - `codeVerifier` must be kept secret and never exposed to the browser.
435
+ - `state` provides CSRF protection and must be validated in the callback.
436
+ - Uses cryptographically secure random generation for `codeVerifier` and `state`.
437
+ - Code challenge is SHA256-hashed (S256 method), not plain text.
438
+
439
+ ---
440
+
351
441
  ## loginPKCE()
352
442
 
353
443
  Perform Authorization Code + PKCE token exchange.
@@ -80,6 +80,7 @@ KeycloakManager.stop();
80
80
  | `setConfig()` | Runtime configuration | Core |
81
81
  | `getToken()` | Get current access token | Core |
82
82
  | `login()` | Preferred OIDC token grant/login endpoint wrapper | Core |
83
+ | `generateAuthorizationUrl()` | Generate PKCE authorization URL and verifier pair | Core |
83
84
  | `loginPKCE()` | Authorization Code + PKCE token exchange helper | Core |
84
85
  | `auth()` | Backward-compatible alias of `login()` | Core |
85
86
  | `stop()` | Stop token refresh timer | Core |
@@ -72,98 +72,51 @@ PKCE (Proof Key for Code Exchange) is the modern, secure way for browser-based a
72
72
 
73
73
  ## Step-by-Step Implementation
74
74
 
75
- ### Step 1: Generate PKCE Pair
75
+ ### Step 1: Generate Authorization URL
76
76
 
77
- When the user clicks "Login", your backend generates a PKCE pair and stores it securely:
77
+ When the user clicks "Login", use `generateAuthorizationUrl()` from keycloak-api-manager to generate the PKCE pair and authorization URL:
78
78
 
79
79
  ```javascript
80
- const crypto = require('crypto');
81
-
82
- function base64url(buf) {
83
- return buf
84
- .toString('base64')
85
- .replace(/\+/g, '-')
86
- .replace(/\//g, '_')
87
- .replace(/=/g, '');
88
- }
80
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl({
81
+ redirect_uri: `${process.env.APP_URL}/auth/callback`,
82
+ scope: 'openid profile email' // optional, defaults to 'openid profile email'
83
+ });
89
84
 
90
- function createPkcePair() {
91
- // Generate code_verifier (random 128-char string)
92
- const code_verifier = base64url(crypto.randomBytes(96));
93
-
94
- // Generate code_challenge (SHA256 hash of verifier)
95
- const code_challenge = base64url(
96
- crypto.createHash('sha256').update(code_verifier).digest()
97
- );
98
-
99
- // Generate state (CSRF protection)
100
- const state = base64url(crypto.randomBytes(32));
101
-
102
- return { code_verifier, code_challenge, state };
103
- }
85
+ // Result contains:
86
+ // {
87
+ // authUrl: 'https://keycloak.example.com/realms/my-realm/protocol/openid-connect/auth?...',
88
+ // state: 'random_state_value',
89
+ // codeVerifier: 'random_verifier_value'
90
+ // }
104
91
  ```
105
92
 
106
- **Why this works:**
107
- - `code_verifier`: A random, unguessable string
108
- - `code_challenge`: The SHA256 hash of the verifier, sent to Keycloak during login
109
- - `state`: A random token to prevent CSRF attacks; Keycloak will return it unchanged
110
-
111
- ### Step 2: Redirect to Keycloak Authorization Endpoint
112
-
113
- Store the PKCE pair in the session, then redirect the user to Keycloak:
93
+ Store the `state` and `codeVerifier` in your session, then redirect the user to the authorization URL:
114
94
 
115
95
  ```javascript
116
- const express = require('express');
117
- const session = require('express-session');
118
- const KeycloakManager = require('keycloak-api-manager');
119
-
120
- const app = express();
121
-
122
- // Session configuration (store verifier securely server-side)
123
- app.use(session({
124
- secret: 'your-secret-key',
125
- resave: false,
126
- saveUninitialized: true,
127
- cookie: { httpOnly: true, secure: true } // secure: true in production (HTTPS only)
128
- }));
129
-
130
- // 1. User clicks "Login" button
131
96
  app.get('/auth/login', (req, res) => {
132
- const { code_verifier, code_challenge, state } = createPkcePair();
133
-
134
- // Store verifier and state in session (server-side only!)
135
- req.session.pkce_verifier = code_verifier;
136
- req.session.pkce_state = state;
137
-
138
- // Build Keycloak authorization URL
139
- const keycloakAuthUrl = `${process.env.KEYCLOAK_URL}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`;
140
- const authUrl = new URL(keycloakAuthUrl);
97
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl({
98
+ redirect_uri: `${process.env.APP_URL}/auth/callback`
99
+ });
141
100
 
142
- authUrl.searchParams.append('client_id', process.env.KEYCLOAK_CLIENT_ID);
143
- authUrl.searchParams.append('redirect_uri', `${process.env.APP_URL}/auth/callback`);
144
- authUrl.searchParams.append('response_type', 'code');
145
- authUrl.searchParams.append('scope', 'openid profile email');
146
- authUrl.searchParams.append('code_challenge', code_challenge);
147
- authUrl.searchParams.append('code_challenge_method', 'S256'); // S256 = SHA256
148
- authUrl.searchParams.append('state', state);
101
+ // Store state and verifier in session for callback validation
102
+ req.session.pkce_state = pkceFlow.state;
103
+ req.session.pkce_verifier = pkceFlow.codeVerifier;
149
104
 
150
- // Redirect user to Keycloak login page
151
- res.redirect(authUrl.toString());
105
+ // Redirect user to Keycloak login
106
+ res.redirect(pkceFlow.authUrl);
152
107
  });
153
108
  ```
154
109
 
155
- **What happens:**
156
- - User is redirected to Keycloak
157
- - They see the login page
158
- - They enter their username/password
159
- - On successful auth, Keycloak redirects back to your `/auth/callback` endpoint with `code` and `state`
160
-
161
- ### Step 3: Exchange Authorization Code for Token
110
+ **Why this works:**
111
+ - `generateAuthorizationUrl()` handles all PKCE complexity internally
112
+ - Returns a ready-to-use authorization URL
113
+ - State parameter provides CSRF protection
114
+ - Code verifier is stored server-side (never exposed to client)
162
115
 
163
- When Keycloak redirects back with the authorization code, you exchange it for an access token:
116
+ When Keycloak redirects back with the authorization code, you exchange it for an access token using `loginPKCE()`:
164
117
 
165
118
  ```javascript
166
- // 2. Keycloak redirects back with authorization code
119
+ // User callback after Keycloak login
167
120
  app.get('/auth/callback', async (req, res) => {
168
121
  try {
169
122
  const { code, state, error } = req.query;
@@ -180,18 +133,15 @@ app.get('/auth/callback', async (req, res) => {
180
133
 
181
134
  // 3. Retrieve stored verifier from session
182
135
  const code_verifier = req.session.pkce_verifier;
183
-
184
136
  if (!code_verifier) {
185
137
  return res.status(400).send('PKCE verifier not found in session');
186
138
  }
187
139
 
188
- // 4. Exchange code for token using loginPKCE()
140
+ // 4. Exchange code for token using keycloak-api-manager
189
141
  const tokenResponse = await KeycloakManager.loginPKCE({
190
142
  code,
191
143
  redirect_uri: `${process.env.APP_URL}/auth/callback`,
192
- code_verifier,
193
- client_id: process.env.KEYCLOAK_CLIENT_ID,
194
- client_secret: process.env.KEYCLOAK_CLIENT_SECRET
144
+ code_verifier
195
145
  });
196
146
 
197
147
  // 5. Set secure HTTPOnly cookie with access token
@@ -232,35 +182,6 @@ app.get('/auth/callback', async (req, res) => {
232
182
  - ✅ Store token in HttpOnly cookie (prevents XSS theft)
233
183
  - ✅ Clear sensitive session data
234
184
 
235
- ### Step 4: Use the Access Token
236
-
237
- Now the user has an access token in a secure cookie. Use it to access protected resources:
238
-
239
- ```javascript
240
- // Middleware to verify access token
241
- app.use((req, res, next) => {
242
- const token = req.cookies.access_token;
243
-
244
- if (!token) {
245
- return res.status(401).send('Not authenticated');
246
- }
247
-
248
- // Verify and decode the token
249
- try {
250
- const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY);
251
- req.user = decoded; // User data available in request
252
- next();
253
- } catch (error) {
254
- return res.status(401).send('Invalid token');
255
- }
256
- });
257
-
258
- // Protected route
259
- app.get('/dashboard', (req, res) => {
260
- res.send(`Welcome, ${req.user.preferred_username}!`);
261
- });
262
- ```
263
-
264
185
  ## Complete Working Example
265
186
 
266
187
  Here's a complete Express.js application with PKCE flow:
@@ -268,7 +189,6 @@ Here's a complete Express.js application with PKCE flow:
268
189
  ```javascript
269
190
  const express = require('express');
270
191
  const session = require('express-session');
271
- const crypto = require('crypto');
272
192
  const jwt = require('jsonwebtoken');
273
193
  const cookieParser = require('cookie-parser');
274
194
  const KeycloakManager = require('keycloak-api-manager');
@@ -287,55 +207,34 @@ app.use(session({
287
207
 
288
208
  // Initialize Keycloak Manager
289
209
  KeycloakManager.configure({
210
+ baseUrl: process.env.KEYCLOAK_URL,
290
211
  realmName: process.env.KEYCLOAK_REALM,
291
212
  clientId: process.env.KEYCLOAK_CLIENT_ID,
292
213
  clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
293
- keycloakUrl: process.env.KEYCLOAK_URL
214
+ grantType: 'client_credentials'
294
215
  });
295
216
 
296
- // Helper functions
297
- function base64url(buf) {
298
- return buf
299
- .toString('base64')
300
- .replace(/\+/g, '-')
301
- .replace(/\//g, '_')
302
- .replace(/=/g, '');
303
- }
304
-
305
- function createPkcePair() {
306
- const code_verifier = base64url(crypto.randomBytes(96));
307
- const code_challenge = base64url(
308
- crypto.createHash('sha256').update(code_verifier).digest()
309
- );
310
- const state = base64url(crypto.randomBytes(32));
311
- return { code_verifier, code_challenge, state };
312
- }
313
-
314
217
  // Routes
315
218
  app.get('/auth/login', (req, res) => {
316
- const { code_verifier, code_challenge, state } = createPkcePair();
317
-
318
- req.session.pkce_verifier = code_verifier;
319
- req.session.pkce_state = state;
320
-
321
- const keycloakAuthUrl = `${process.env.KEYCLOAK_URL}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`;
322
- const authUrl = new URL(keycloakAuthUrl);
219
+ // Generate PKCE pair and authorization URL
220
+ const pkceFlow = KeycloakManager.generateAuthorizationUrl({
221
+ redirect_uri: `${process.env.APP_URL}/auth/callback`,
222
+ scope: 'openid profile email'
223
+ });
323
224
 
324
- authUrl.searchParams.append('client_id', process.env.KEYCLOAK_CLIENT_ID);
325
- authUrl.searchParams.append('redirect_uri', `${process.env.APP_URL}/auth/callback`);
326
- authUrl.searchParams.append('response_type', 'code');
327
- authUrl.searchParams.append('scope', 'openid profile email');
328
- authUrl.searchParams.append('code_challenge', code_challenge);
329
- authUrl.searchParams.append('code_challenge_method', 'S256');
330
- authUrl.searchParams.append('state', state);
225
+ // Store state and verifier in session (server-side only!)
226
+ req.session.pkce_state = pkceFlow.state;
227
+ req.session.pkce_verifier = pkceFlow.codeVerifier;
331
228
 
332
- res.redirect(authUrl.toString());
229
+ // Redirect user to Keycloak login
230
+ res.redirect(pkceFlow.authUrl);
333
231
  });
334
232
 
335
233
  app.get('/auth/callback', async (req, res) => {
336
234
  try {
337
235
  const { code, state, error } = req.query;
338
236
 
237
+ // Validate state (CSRF protection)
339
238
  if (state !== req.session.pkce_state) {
340
239
  return res.status(400).send('Invalid state parameter');
341
240
  }
@@ -349,13 +248,11 @@ app.get('/auth/callback', async (req, res) => {
349
248
  return res.status(400).send('PKCE verifier not found');
350
249
  }
351
250
 
352
- // Exchange code for token
251
+ // Exchange code for token (client_id + client_secret from configure())
353
252
  const tokenResponse = await KeycloakManager.loginPKCE({
354
253
  code,
355
254
  redirect_uri: `${process.env.APP_URL}/auth/callback`,
356
- code_verifier,
357
- client_id: process.env.KEYCLOAK_CLIENT_ID,
358
- client_secret: process.env.KEYCLOAK_CLIENT_SECRET
255
+ code_verifier
359
256
  });
360
257
 
361
258
  // Set secure cookies
@@ -373,6 +270,7 @@ app.get('/auth/callback', async (req, res) => {
373
270
  maxAge: 7 * 24 * 60 * 60 * 1000
374
271
  });
375
272
 
273
+ // Clear sensitive data
376
274
  delete req.session.pkce_verifier;
377
275
  delete req.session.pkce_state;
378
276
 
package/index.js CHANGED
@@ -177,14 +177,50 @@ async function requestOidcToken(credentials = {}) {
177
177
  return payload;
178
178
  }
179
179
 
180
+ /**
181
+ * @deprecated v6.0.0 - This method has been moved to keycloak-express-middleware.
182
+ * Use the middleware package for user authentication instead. See:
183
+ * https://github.com/smartenv-crs4/keycloak-express-middleware#oidc-authentication
184
+ *
185
+ * This API manager is intended for Keycloak admin resource management only.
186
+ * For user authentication flows, import from keycloak-express-middleware.
187
+ *
188
+ * @see {@link https://github.com/smartenv-crs4/keycloak-express-middleware|keycloak-express-middleware}
189
+ * @param {object} credentials - OIDC token request credentials
190
+ * @returns {Promise<object>} Token response containing access_token, refresh_token, etc.
191
+ */
180
192
  exports.auth = async function auth(credentials = {}) {
181
193
  return requestOidcToken(credentials);
182
194
  };
183
195
 
196
+ /**
197
+ * @deprecated v6.0.0 - This method has been moved to keycloak-express-middleware.
198
+ * Use the middleware package for user authentication instead. See:
199
+ * https://github.com/smartenv-crs4/keycloak-express-middleware#oidc-authentication
200
+ *
201
+ * This API manager is intended for Keycloak admin resource management only.
202
+ * For user authentication flows, import from keycloak-express-middleware.
203
+ *
204
+ * @see {@link https://github.com/smartenv-crs4/keycloak-express-middleware|keycloak-express-middleware}
205
+ * @param {object} credentials - OIDC token request credentials (supports any OAuth2 grant type)
206
+ * @returns {Promise<object>} Token response containing access_token, refresh_token, etc.
207
+ */
184
208
  exports.login = async function login(credentials = {}) {
185
209
  return requestOidcToken(credentials);
186
210
  };
187
211
 
212
+ /**
213
+ * @deprecated v6.0.0 - This method has been moved to keycloak-express-middleware.
214
+ * Use the middleware package for user authentication instead. See:
215
+ * https://github.com/smartenv-crs4/keycloak-express-middleware#oidc-pkce-flow
216
+ *
217
+ * This API manager is intended for Keycloak admin resource management only.
218
+ * For user authentication flows, import from keycloak-express-middleware.
219
+ *
220
+ * @see {@link https://github.com/smartenv-crs4/keycloak-express-middleware|keycloak-express-middleware}
221
+ * @param {object} credentials - PKCE authorization code exchange parameters
222
+ * @returns {Promise<object>} Token response containing access_token, refresh_token, etc.
223
+ */
188
224
  exports.loginPKCE = async function loginPKCE(credentials = {}) {
189
225
  const {
190
226
  code,
@@ -225,3 +261,75 @@ exports.loginPKCE = async function loginPKCE(credentials = {}) {
225
261
  ...rest
226
262
  });
227
263
  };
264
+
265
+ /**
266
+ * @deprecated v6.0.0 - This method has been moved to keycloak-express-middleware.
267
+ * Use the middleware package for user authentication instead. See:
268
+ * https://github.com/smartenv-crs4/keycloak-express-middleware#generating-authorization-urls
269
+ *
270
+ * This API manager is intended for Keycloak admin resource management only.
271
+ * For user authentication flows, import from keycloak-express-middleware.
272
+ *
273
+ * @see {@link https://github.com/smartenv-crs4/keycloak-express-middleware|keycloak-express-middleware}
274
+ * @param {object} options - Authorization URL generation options
275
+ * @returns {object} Object with { authUrl, state, codeVerifier } for PKCE flow
276
+ */
277
+ exports.generateAuthorizationUrl = function generateAuthorizationUrl(options = {}) {
278
+ assertConfigured();
279
+
280
+ const {
281
+ redirect_uri,
282
+ redirectUri,
283
+ scope,
284
+ state: customState
285
+ } = options;
286
+
287
+ const resolvedRedirectUri = redirect_uri || redirectUri;
288
+ if (!resolvedRedirectUri) {
289
+ throw new Error('generateAuthorizationUrl requires "redirect_uri" (or "redirectUri").');
290
+ }
291
+
292
+ const crypto = require('crypto');
293
+
294
+ // Helper to encode bytes to base64url
295
+ function base64url(buffer) {
296
+ return buffer
297
+ .toString('base64')
298
+ .replace(/\+/g, '-')
299
+ .replace(/\//g, '_')
300
+ .replace(/=/g, '');
301
+ }
302
+
303
+ // Generate PKCE pair
304
+ const codeVerifier = base64url(crypto.randomBytes(96));
305
+ const codeChallenge = base64url(
306
+ crypto.createHash('sha256').update(codeVerifier).digest()
307
+ );
308
+
309
+ // Generate or use provided state
310
+ const state = customState || base64url(crypto.randomBytes(32));
311
+
312
+ // Build authorization URL
313
+ const authUrl = new URL(
314
+ `${runtimeConfig.baseUrl}/realms/${runtimeConfig.realmName}/protocol/openid-connect/auth`
315
+ );
316
+
317
+ authUrl.searchParams.append('client_id', runtimeConfig.clientId);
318
+ authUrl.searchParams.append('response_type', 'code');
319
+ authUrl.searchParams.append('redirect_uri', resolvedRedirectUri);
320
+ authUrl.searchParams.append('code_challenge', codeChallenge);
321
+ authUrl.searchParams.append('code_challenge_method', 'S256');
322
+ authUrl.searchParams.append('state', state);
323
+
324
+ if (scope) {
325
+ authUrl.searchParams.append('scope', scope);
326
+ } else {
327
+ authUrl.searchParams.append('scope', 'openid profile email');
328
+ }
329
+
330
+ return {
331
+ authUrl: authUrl.toString(),
332
+ state,
333
+ codeVerifier
334
+ };
335
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keycloak-api-manager",
3
- "version": "5.0.7",
3
+ "version": "6.0.0",
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": {