keycloak-api-manager 5.0.7 → 6.0.1
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/OIDC_MIGRATION_PLAN.md +294 -0
- package/README.md +19 -9
- package/docs/api/configuration.md +123 -3
- package/docs/api-reference.md +1 -0
- package/docs/guides/PKCE-Login-Flow.md +205 -148
- package/index.js +108 -0
- package/package.json +1 -1
- package/test-output.log +0 -72
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# OIDC Methods Migration Plan - keycloak-api-manager
|
|
2
|
+
|
|
3
|
+
## 🚀 Current Status: v6.0.0 - MIGRATION RELEASED
|
|
4
|
+
|
|
5
|
+
✅ **OIDC Methods Deprecated:** All OIDC authentication methods are now marked `@deprecated` in v6.0.0.
|
|
6
|
+
|
|
7
|
+
✅ **keycloak-express-middleware v6.1.0 Released:** OIDC methods now available in middleware package with full test coverage.
|
|
8
|
+
|
|
9
|
+
📅 **Removal Timeline:**
|
|
10
|
+
- **v6.0.0** (NOW) - Methods work but marked @deprecated
|
|
11
|
+
- **v7.0.0** (FUTURE) - Methods will be permanently removed
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
L'architettura prevede una **migrazione pianificata** dei metodi OIDC (`auth()`, `login()`, `loginPKCE()`) da `keycloak-api-manager` a `keycloak-express-middleware`.
|
|
18
|
+
|
|
19
|
+
## Current State (v5.0.8)
|
|
20
|
+
|
|
21
|
+
**In keycloak-api-manager:**
|
|
22
|
+
- ✅ `auth(credentials)` - OIDC token endpoint wrapper
|
|
23
|
+
- ✅ `login(credentials)` - Generic token grant helper
|
|
24
|
+
- ✅ `generateAuthorizationUrl(options)` - PKCE URL generator
|
|
25
|
+
- ✅ `loginPKCE(credentials)` - PKCE token exchange
|
|
26
|
+
|
|
27
|
+
## Current State (v6.0.0) - NOW
|
|
28
|
+
|
|
29
|
+
**Status Changes in keycloak-api-manager:**
|
|
30
|
+
- ⚠️ `auth(credentials)` - Marked @deprecated
|
|
31
|
+
- ⚠️ `login(credentials)` - Marked @deprecated
|
|
32
|
+
- ⚠️ `generateAuthorizationUrl(options)` - Marked @deprecated
|
|
33
|
+
- ⚠️ `loginPKCE(credentials)` - Marked @deprecated
|
|
34
|
+
|
|
35
|
+
✅ Methods **still work** but show deprecation warnings in JSDoc and IDE.
|
|
36
|
+
|
|
37
|
+
**Status in keycloak-express-middleware v6.1.0:**
|
|
38
|
+
- ✅ `generateAuthorizationUrl(options)` - Fully implemented & tested
|
|
39
|
+
- ✅ `login(credentials)` - Fully implemented & tested
|
|
40
|
+
- ✅ `loginPKCE(credentials)` - Fully implemented & tested
|
|
41
|
+
- ✅ All 21 tests passing
|
|
42
|
+
|
|
43
|
+
## Why This Migration?
|
|
44
|
+
|
|
45
|
+
### Separation of Concerns
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
keycloak-api-manager (Admin API Management)
|
|
49
|
+
├── configure() ← Admin setup
|
|
50
|
+
├── Users.find() ← Admin operations
|
|
51
|
+
├── Realms.create() ← Admin operations
|
|
52
|
+
└── ❌ login() / auth() ← Should NOT be here!
|
|
53
|
+
|
|
54
|
+
keycloak-express-middleware (User Authentication)
|
|
55
|
+
├── protectMiddleware() ← Route protection
|
|
56
|
+
├── Express integration ← Session, cookies
|
|
57
|
+
└── ✅ generateAuthorizationUrl() ← Belongs here
|
|
58
|
+
└── ✅ login() ← Belongs here
|
|
59
|
+
└── ✅ loginPKCE() ← Belongs here
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Why Middleware is Better
|
|
63
|
+
|
|
64
|
+
1. **Natural Context:** Express app has sessions, cookies, redirects
|
|
65
|
+
2. **No Admin Context:** Users don't need admin API management
|
|
66
|
+
3. **Tighter Integration:** Works with middleware ecosystem
|
|
67
|
+
4. **Cleaner Namespaces:** Each package has clear responsibilities
|
|
68
|
+
|
|
69
|
+
## Migration Timeline
|
|
70
|
+
|
|
71
|
+
### Phase 1: ✅ DONE (Now)
|
|
72
|
+
- [x] Create ready-to-integrate files in middleware (`oidc-methods.js`)
|
|
73
|
+
- [x] Write comprehensive tests (21 tests, all passing)
|
|
74
|
+
- [x] Document integration guide
|
|
75
|
+
- [x] Commit to middleware repo
|
|
76
|
+
- [x] Integrate OIDC methods into keycloak-express-middleware v6.1.0
|
|
77
|
+
- [x] Release keycloak-express-middleware v6.1.0 to npm
|
|
78
|
+
|
|
79
|
+
**Status:** ✅ COMPLETE & RELEASED
|
|
80
|
+
|
|
81
|
+
### Phase 2: ✅ DONE (Done)
|
|
82
|
+
- [x] Mark methods as @deprecated in keycloak-api-manager index.js
|
|
83
|
+
- [x] Update README with deprecation notices
|
|
84
|
+
- [x] Update API documentation with deprecation notices
|
|
85
|
+
- [x] Add migration guide in PKCE-Login-Flow docs
|
|
86
|
+
- [x] Release keycloak-api-manager v6.0.0
|
|
87
|
+
|
|
88
|
+
**Status:** ✅ COMPLETE - v6.0.0 released with @deprecated warnings
|
|
89
|
+
|
|
90
|
+
### Phase 3: TODO (Future - v7.0.0)
|
|
91
|
+
- [ ] Remove OIDC methods from keycloak-api-manager
|
|
92
|
+
- [ ] Release keycloak-api-manager v7.0.0 (breaking change)
|
|
93
|
+
- [ ] Update all documentation to reference keycloak-express-middleware only
|
|
94
|
+
|
|
95
|
+
**Timeline:** TBD based on user feedback and adoption
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## What You Need to Know
|
|
100
|
+
|
|
101
|
+
### For Current Users (v5.0.8 or earlier)
|
|
102
|
+
|
|
103
|
+
**Action Required:** Migrate to keycloak-express-middleware before v7.0.0.
|
|
104
|
+
|
|
105
|
+
**Timeline:** v6.0.0 is deprecated (warnings only), v7.0.0 will remove methods entirely.
|
|
106
|
+
|
|
107
|
+
**Steps:**
|
|
108
|
+
1. Install keycloak-express-middleware v6.1.0+
|
|
109
|
+
2. Replace method calls (see examples below)
|
|
110
|
+
3. Test thoroughly
|
|
111
|
+
4. Deploy before v7.0.0 is released
|
|
112
|
+
|
|
113
|
+
### For New Projects
|
|
114
|
+
|
|
115
|
+
**Action Required:** Do NOT use OIDC methods from keycloak-api-manager.
|
|
116
|
+
|
|
117
|
+
**Recommendation:** Use keycloak-express-middleware v6.1.0+ for all user authentication.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## NPM Packages
|
|
122
|
+
|
|
123
|
+
### keycloak-api-manager
|
|
124
|
+
|
|
125
|
+
| Version | OIDC Methods | Status |
|
|
126
|
+
|---------|------------|--------|
|
|
127
|
+
| v5.0.8 | Supported | Legacy (should migrate) |
|
|
128
|
+
| v6.0.0 | Deprecated | Current (shows warnings) |
|
|
129
|
+
| v7.0.0 | Removed | Future (no OIDC methods) |
|
|
130
|
+
|
|
131
|
+
**NPM:** https://www.npmjs.com/package/keycloak-api-manager
|
|
132
|
+
|
|
133
|
+
### keycloak-express-middleware
|
|
134
|
+
|
|
135
|
+
| Version | OIDC Methods | Status |
|
|
136
|
+
|---------|------------|--------|
|
|
137
|
+
| < 6.1.0 | Not available | Legacy |
|
|
138
|
+
| v6.1.0+ | Available | Current - RECOMMENDED |
|
|
139
|
+
|
|
140
|
+
**NPM:** https://www.npmjs.com/package/keycloak-express-middleware
|
|
141
|
+
|
|
142
|
+
**GitHub:** https://github.com/smartenv-crs4/keycloak-express-middleware
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `auth(credentials)` - Generic OIDC Token Grant (DEPRECATED)
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// ⚠️ DEPRECATED - Don't use in new code
|
|
150
|
+
const token = await KeycloakManager.auth({
|
|
151
|
+
grant_type: 'password',
|
|
152
|
+
username: 'user',
|
|
153
|
+
password: 'pass'
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Migration Path:**
|
|
158
|
+
```javascript
|
|
159
|
+
// ✅ NEW - Use keycloak-express-middleware instead
|
|
160
|
+
const token = await keycloakMiddleware.login({
|
|
161
|
+
grant_type: 'password',
|
|
162
|
+
username: 'user',
|
|
163
|
+
password: 'pass'
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### `login(credentials)` - Preferred Alias (DEPRECATED)
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
// ⚠️ DEPRECATED - Don't use in new code
|
|
173
|
+
const token = await KeycloakManager.login({
|
|
174
|
+
grant_type: 'client_credentials'
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Migration Path:**
|
|
179
|
+
```javascript
|
|
180
|
+
// ✅ NEW - Use keycloak-express-middleware instead
|
|
181
|
+
const token = await keycloakMiddleware.login({
|
|
182
|
+
grant_type: 'client_credentials'
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### `generateAuthorizationUrl(options)` - PKCE URL Generator (DEPRECATED)
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
// ⚠️ DEPRECATED - Don't use in new code
|
|
192
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
193
|
+
redirect_uri: 'https://app/callback'
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Migration Path:**
|
|
198
|
+
```javascript
|
|
199
|
+
// ✅ NEW - Use keycloak-express-middleware instead
|
|
200
|
+
const pkceFlow = keycloakMiddleware.generateAuthorizationUrl({
|
|
201
|
+
redirect_uri: 'https://app/callback'
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `loginPKCE(credentials)` - PKCE Token Exchange (DEPRECATED)
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// ⚠️ DEPRECATED - Don't use in new code
|
|
211
|
+
const token = await KeycloakManager.loginPKCE({
|
|
212
|
+
code: req.query.code,
|
|
213
|
+
redirect_uri: 'https://app/callback',
|
|
214
|
+
code_verifier: req.session.verifier
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Migration Path:**
|
|
219
|
+
```javascript
|
|
220
|
+
// ✅ NEW - Use keycloak-express-middleware instead
|
|
221
|
+
const token = await keycloakMiddleware.loginPKCE({
|
|
222
|
+
code: req.query.code,
|
|
223
|
+
redirect_uri: 'https://app/callback',
|
|
224
|
+
code_verifier: req.session.verifier
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Migration Path:**
|
|
229
|
+
```javascript
|
|
230
|
+
// Move to middleware
|
|
231
|
+
const token = await keycloakMiddleware.loginPKCE({
|
|
232
|
+
code: req.query.code,
|
|
233
|
+
redirect_uri: 'https://app/callback',
|
|
234
|
+
code_verifier: req.session.verifier
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## What Stays in keycloak-api-manager
|
|
241
|
+
|
|
242
|
+
✅ **These remain unchanged forever:**
|
|
243
|
+
- `configure(credentials)` - Admin authentication
|
|
244
|
+
- `setConfig(overrides)` - Configuration management
|
|
245
|
+
- `getToken()` - Get current admin token
|
|
246
|
+
- `stop()` - Cleanup
|
|
247
|
+
- **All handlers:** `users`, `realms`, `clients`, `groups`, `roles`, etc.
|
|
248
|
+
|
|
249
|
+
The package remains the **dedicated Admin API Manager** - exactly what it should be.
|
|
250
|
+
|
|
251
|
+
## Migration Guide for Users (TBD)
|
|
252
|
+
|
|
253
|
+
When ready, we'll publish:
|
|
254
|
+
|
|
255
|
+
```markdown
|
|
256
|
+
# Migrating OIDC Methods from keycloak-api-manager to keycloak-express-middleware
|
|
257
|
+
|
|
258
|
+
## Version Support
|
|
259
|
+
|
|
260
|
+
| Method | deprecated | removed | alternative |
|
|
261
|
+
|--------|-----------|---------|-------------|
|
|
262
|
+
| `auth()` | v6.0.0 | v7.0.0 | middleware.login() |
|
|
263
|
+
| `login()` | v6.0.0 | v7.0.0 | middleware.login() |
|
|
264
|
+
| `generateAuthorizationUrl()` | v6.0.0 | v7.0.0 | middleware.generateAuthorizationUrl() |
|
|
265
|
+
| `loginPKCE()` | v6.0.0 | v7.0.0 | middleware.loginPKCE() |
|
|
266
|
+
|
|
267
|
+
## Migration Steps
|
|
268
|
+
|
|
269
|
+
1. Install/update middleware
|
|
270
|
+
2. Replace method calls
|
|
271
|
+
3. Update imports
|
|
272
|
+
4. Test
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Next Actions
|
|
276
|
+
|
|
277
|
+
### You Should Do:
|
|
278
|
+
1. Review `keycloak-express-middleware/OIDC_INTEGRATION_GUIDE.md`
|
|
279
|
+
2. Decide if you want to integrate manually or have me automate it
|
|
280
|
+
3. Once middleware is ready, let me know
|
|
281
|
+
|
|
282
|
+
### I Will Do (When You Say):
|
|
283
|
+
1. Integrate methods into middleware `index.js`
|
|
284
|
+
2. Release `keycloak-express-middleware v6.1.0`
|
|
285
|
+
3. Deprecate in `keycloak-api-manager v6.0.0`
|
|
286
|
+
4. Create migration guide
|
|
287
|
+
5. Update all documentation
|
|
288
|
+
|
|
289
|
+
## Questions?
|
|
290
|
+
|
|
291
|
+
- How should we handle the transition period?
|
|
292
|
+
- Do we support both packages simultaneously?
|
|
293
|
+
- Timeline for full migration?
|
|
294
|
+
- 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
|
|
@@ -48,6 +54,8 @@ console.log(users.length);
|
|
|
48
54
|
KeycloakManager.stop();
|
|
49
55
|
```
|
|
50
56
|
|
|
57
|
+
> **💡 Tip:** For user authentication (login with credentials, PKCE flow, token exchange), use [`keycloak-express-middleware`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead. See [PKCE Login Flow Guide](docs/guides/PKCE-Login-Flow.md#-migration-to-keycloak-express-middleware-recommended) for migration examples.
|
|
58
|
+
|
|
51
59
|
## Keycloak Feature Flags
|
|
52
60
|
|
|
53
61
|
For full API coverage in this package (especially Organizations, Client Policies, User Profile, Group permissions), run Keycloak with:
|
|
@@ -65,16 +73,13 @@ In Keycloak 26.x, management-permissions APIs used by group/user fine-grained te
|
|
|
65
73
|
- `configure(credentials)`
|
|
66
74
|
- `setConfig(overrides)`
|
|
67
75
|
- `getToken()`
|
|
68
|
-
- `login(credentials)`
|
|
69
|
-
- `loginPKCE(credentials)`
|
|
70
|
-
- `auth(credentials)`
|
|
71
76
|
- `stop()`
|
|
77
|
+
- ~~`login(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
|
|
78
|
+
- ~~`generateAuthorizationUrl(options)`~~ **DEPRECATED** - moved to keycloak-express-middleware
|
|
79
|
+
- ~~`loginPKCE(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
|
|
80
|
+
- ~~`auth(credentials)`~~ **DEPRECATED** - moved to keycloak-express-middleware
|
|
72
81
|
|
|
73
|
-
|
|
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()`.
|
|
82
|
+
**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
83
|
|
|
79
84
|
Configured handler namespaces:
|
|
80
85
|
|
|
@@ -158,10 +163,15 @@ docs/ # Centralized documentation
|
|
|
158
163
|
|
|
159
164
|
## Versioning and Compatibility
|
|
160
165
|
|
|
161
|
-
- Package version: `
|
|
166
|
+
- Package version: `6.0.0`
|
|
162
167
|
- Keycloak Admin client dependency: `@keycloak/keycloak-admin-client`
|
|
163
168
|
- Main compatibility target: Keycloak 25/26
|
|
164
169
|
|
|
170
|
+
### Breaking Changes in v6.0.0
|
|
171
|
+
|
|
172
|
+
OIDC authentication methods (`login()`, `loginPKCE()`, `generateAuthorizationUrl()`, `auth()`) are now deprecated.
|
|
173
|
+
These methods will be removed in v7.0.0. Migrate to `keycloak-express-middleware` for user authentication.
|
|
174
|
+
|
|
165
175
|
## License
|
|
166
176
|
|
|
167
177
|
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)
|
|
@@ -238,9 +239,15 @@ const response = await axios.get('https://keycloak.example.com/admin/realms/mast
|
|
|
238
239
|
|
|
239
240
|
## auth()
|
|
240
241
|
|
|
241
|
-
|
|
242
|
+
⚠️ **DEPRECATED (v6.0.0)** - Use [`keycloak-express-middleware.login()`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead.
|
|
242
243
|
|
|
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).
|
|
244
251
|
|
|
245
252
|
**Syntax:**
|
|
246
253
|
```javascript
|
|
@@ -255,6 +262,14 @@ await KeycloakManager.auth(credentials)
|
|
|
255
262
|
|
|
256
263
|
## login()
|
|
257
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)
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
258
273
|
Request tokens from Keycloak via the OIDC token endpoint.
|
|
259
274
|
|
|
260
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.
|
|
@@ -348,13 +363,118 @@ console.log(refreshed.access_token);
|
|
|
348
363
|
|
|
349
364
|
---
|
|
350
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
|
+
|
|
351
463
|
## loginPKCE()
|
|
352
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
|
+
|
|
353
473
|
Perform Authorization Code + PKCE token exchange.
|
|
354
474
|
|
|
355
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`.
|
|
356
476
|
|
|
357
|
-
> **📖 For a complete step-by-step guide on implementing PKCE flow in your application, see [PKCE Login Flow Guide](../guides/PKCE-Login-Flow.md)**
|
|
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)**
|
|
358
478
|
|
|
359
479
|
**Syntax:**
|
|
360
480
|
```javascript
|
package/docs/api-reference.md
CHANGED
|
@@ -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 |
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# PKCE Login Flow Guide
|
|
2
2
|
|
|
3
|
+
⚠️ **DEPRECATION NOTICE (v6.0.0):** This guide describes PKCE implementation using **deprecated methods** in keycloak-api-manager.
|
|
4
|
+
|
|
5
|
+
**👉 NEW APPROACH (Recommended):** For user authentication flows, use [`keycloak-express-middleware v6.1.0+`](https://github.com/smartenv-crs4/keycloak-express-middleware) instead. The middleware package provides a more integrated and Express-native implementation of PKCE flows.
|
|
6
|
+
|
|
7
|
+
**See:** Migration instructions at the end of this guide.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
3
11
|
This guide walks you through implementing OAuth2 Authorization Code + PKCE flow in your application using Keycloak and the keycloak-api-manager library.
|
|
4
12
|
|
|
5
13
|
## Overview
|
|
@@ -72,98 +80,51 @@ PKCE (Proof Key for Code Exchange) is the modern, secure way for browser-based a
|
|
|
72
80
|
|
|
73
81
|
## Step-by-Step Implementation
|
|
74
82
|
|
|
75
|
-
### Step 1: Generate
|
|
83
|
+
### Step 1: Generate Authorization URL
|
|
76
84
|
|
|
77
|
-
When the user clicks "Login",
|
|
85
|
+
When the user clicks "Login", use `generateAuthorizationUrl()` from keycloak-api-manager to generate the PKCE pair and authorization URL:
|
|
78
86
|
|
|
79
87
|
```javascript
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.toString('base64')
|
|
85
|
-
.replace(/\+/g, '-')
|
|
86
|
-
.replace(/\//g, '_')
|
|
87
|
-
.replace(/=/g, '');
|
|
88
|
-
}
|
|
88
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
89
|
+
redirect_uri: `${process.env.APP_URL}/auth/callback`,
|
|
90
|
+
scope: 'openid profile email' // optional, defaults to 'openid profile email'
|
|
91
|
+
});
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
93
|
+
// Result contains:
|
|
94
|
+
// {
|
|
95
|
+
// authUrl: 'https://keycloak.example.com/realms/my-realm/protocol/openid-connect/auth?...',
|
|
96
|
+
// state: 'random_state_value',
|
|
97
|
+
// codeVerifier: 'random_verifier_value'
|
|
98
|
+
// }
|
|
104
99
|
```
|
|
105
100
|
|
|
106
|
-
|
|
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:
|
|
101
|
+
Store the `state` and `codeVerifier` in your session, then redirect the user to the authorization URL:
|
|
114
102
|
|
|
115
103
|
```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
104
|
app.get('/auth/login', (req, res) => {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
105
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
106
|
+
redirect_uri: `${process.env.APP_URL}/auth/callback`
|
|
107
|
+
});
|
|
141
108
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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);
|
|
109
|
+
// Store state and verifier in session for callback validation
|
|
110
|
+
req.session.pkce_state = pkceFlow.state;
|
|
111
|
+
req.session.pkce_verifier = pkceFlow.codeVerifier;
|
|
149
112
|
|
|
150
|
-
// Redirect user to Keycloak login
|
|
151
|
-
res.redirect(authUrl
|
|
113
|
+
// Redirect user to Keycloak login
|
|
114
|
+
res.redirect(pkceFlow.authUrl);
|
|
152
115
|
});
|
|
153
116
|
```
|
|
154
117
|
|
|
155
|
-
**
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
### Step 3: Exchange Authorization Code for Token
|
|
118
|
+
**Why this works:**
|
|
119
|
+
- `generateAuthorizationUrl()` handles all PKCE complexity internally
|
|
120
|
+
- Returns a ready-to-use authorization URL
|
|
121
|
+
- State parameter provides CSRF protection
|
|
122
|
+
- Code verifier is stored server-side (never exposed to client)
|
|
162
123
|
|
|
163
|
-
When Keycloak redirects back with the authorization code, you exchange it for an access token
|
|
124
|
+
When Keycloak redirects back with the authorization code, you exchange it for an access token using `loginPKCE()`:
|
|
164
125
|
|
|
165
126
|
```javascript
|
|
166
|
-
//
|
|
127
|
+
// User callback after Keycloak login
|
|
167
128
|
app.get('/auth/callback', async (req, res) => {
|
|
168
129
|
try {
|
|
169
130
|
const { code, state, error } = req.query;
|
|
@@ -180,18 +141,15 @@ app.get('/auth/callback', async (req, res) => {
|
|
|
180
141
|
|
|
181
142
|
// 3. Retrieve stored verifier from session
|
|
182
143
|
const code_verifier = req.session.pkce_verifier;
|
|
183
|
-
|
|
184
144
|
if (!code_verifier) {
|
|
185
145
|
return res.status(400).send('PKCE verifier not found in session');
|
|
186
146
|
}
|
|
187
147
|
|
|
188
|
-
// 4. Exchange code for token using
|
|
148
|
+
// 4. Exchange code for token using keycloak-api-manager
|
|
189
149
|
const tokenResponse = await KeycloakManager.loginPKCE({
|
|
190
150
|
code,
|
|
191
151
|
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
|
|
152
|
+
code_verifier
|
|
195
153
|
});
|
|
196
154
|
|
|
197
155
|
// 5. Set secure HTTPOnly cookie with access token
|
|
@@ -232,35 +190,6 @@ app.get('/auth/callback', async (req, res) => {
|
|
|
232
190
|
- ✅ Store token in HttpOnly cookie (prevents XSS theft)
|
|
233
191
|
- ✅ Clear sensitive session data
|
|
234
192
|
|
|
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
193
|
## Complete Working Example
|
|
265
194
|
|
|
266
195
|
Here's a complete Express.js application with PKCE flow:
|
|
@@ -268,7 +197,6 @@ Here's a complete Express.js application with PKCE flow:
|
|
|
268
197
|
```javascript
|
|
269
198
|
const express = require('express');
|
|
270
199
|
const session = require('express-session');
|
|
271
|
-
const crypto = require('crypto');
|
|
272
200
|
const jwt = require('jsonwebtoken');
|
|
273
201
|
const cookieParser = require('cookie-parser');
|
|
274
202
|
const KeycloakManager = require('keycloak-api-manager');
|
|
@@ -287,55 +215,34 @@ app.use(session({
|
|
|
287
215
|
|
|
288
216
|
// Initialize Keycloak Manager
|
|
289
217
|
KeycloakManager.configure({
|
|
218
|
+
baseUrl: process.env.KEYCLOAK_URL,
|
|
290
219
|
realmName: process.env.KEYCLOAK_REALM,
|
|
291
220
|
clientId: process.env.KEYCLOAK_CLIENT_ID,
|
|
292
221
|
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
|
|
293
|
-
|
|
222
|
+
grantType: 'client_credentials'
|
|
294
223
|
});
|
|
295
224
|
|
|
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
225
|
// Routes
|
|
315
226
|
app.get('/auth/login', (req, res) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const keycloakAuthUrl = `${process.env.KEYCLOAK_URL}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`;
|
|
322
|
-
const authUrl = new URL(keycloakAuthUrl);
|
|
227
|
+
// Generate PKCE pair and authorization URL
|
|
228
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
229
|
+
redirect_uri: `${process.env.APP_URL}/auth/callback`,
|
|
230
|
+
scope: 'openid profile email'
|
|
231
|
+
});
|
|
323
232
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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);
|
|
233
|
+
// Store state and verifier in session (server-side only!)
|
|
234
|
+
req.session.pkce_state = pkceFlow.state;
|
|
235
|
+
req.session.pkce_verifier = pkceFlow.codeVerifier;
|
|
331
236
|
|
|
332
|
-
|
|
237
|
+
// Redirect user to Keycloak login
|
|
238
|
+
res.redirect(pkceFlow.authUrl);
|
|
333
239
|
});
|
|
334
240
|
|
|
335
241
|
app.get('/auth/callback', async (req, res) => {
|
|
336
242
|
try {
|
|
337
243
|
const { code, state, error } = req.query;
|
|
338
244
|
|
|
245
|
+
// Validate state (CSRF protection)
|
|
339
246
|
if (state !== req.session.pkce_state) {
|
|
340
247
|
return res.status(400).send('Invalid state parameter');
|
|
341
248
|
}
|
|
@@ -349,13 +256,11 @@ app.get('/auth/callback', async (req, res) => {
|
|
|
349
256
|
return res.status(400).send('PKCE verifier not found');
|
|
350
257
|
}
|
|
351
258
|
|
|
352
|
-
// Exchange code for token
|
|
259
|
+
// Exchange code for token (client_id + client_secret from configure())
|
|
353
260
|
const tokenResponse = await KeycloakManager.loginPKCE({
|
|
354
261
|
code,
|
|
355
262
|
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
|
|
263
|
+
code_verifier
|
|
359
264
|
});
|
|
360
265
|
|
|
361
266
|
// Set secure cookies
|
|
@@ -373,6 +278,7 @@ app.get('/auth/callback', async (req, res) => {
|
|
|
373
278
|
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
374
279
|
});
|
|
375
280
|
|
|
281
|
+
// Clear sensitive data
|
|
376
282
|
delete req.session.pkce_verifier;
|
|
377
283
|
delete req.session.pkce_state;
|
|
378
284
|
|
|
@@ -608,3 +514,154 @@ function verifyToken(token) {
|
|
|
608
514
|
- [login() API Reference](../api/configuration.md#login)
|
|
609
515
|
- [OAuth2 PKCE Specification](https://tools.ietf.org/html/rfc7636)
|
|
610
516
|
- [Keycloak Authorization Code Flow](https://www.keycloak.org/docs/latest/server_admin/#_oidc)
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 🚀 Migration to keycloak-express-middleware (Recommended)
|
|
521
|
+
|
|
522
|
+
The methods described in this guide (`generateAuthorizationUrl()`, `loginPKCE()`, `login()`) are **deprecated in v6.0.0** and will be removed in v7.0.0.
|
|
523
|
+
|
|
524
|
+
### Why Migrate?
|
|
525
|
+
|
|
526
|
+
`keycloak-express-middleware` provides:
|
|
527
|
+
- ✅ Native Express integration (sessions, cookies, redirects)
|
|
528
|
+
- ✅ Cleaner PKCE implementation focused on user authentication
|
|
529
|
+
- ✅ Better separation of concerns (admin API vs user auth)
|
|
530
|
+
- ✅ Tighter integration with Express middleware patterns
|
|
531
|
+
|
|
532
|
+
### Migration Example
|
|
533
|
+
|
|
534
|
+
**Old Code (keycloak-api-manager, DEPRECATED):**
|
|
535
|
+
```javascript
|
|
536
|
+
const KeycloakManager = require('keycloak-api-manager');
|
|
537
|
+
|
|
538
|
+
// Configure admin API
|
|
539
|
+
await KeycloakManager.configure({
|
|
540
|
+
baseUrl: 'https://keycloak:8443',
|
|
541
|
+
realmName: 'master',
|
|
542
|
+
clientId: 'admin-cli',
|
|
543
|
+
username: 'admin',
|
|
544
|
+
password: 'admin'
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Use OIDC methods (deprecated)
|
|
548
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({
|
|
549
|
+
redirect_uri: 'http://localhost:3000/callback'
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const tokens = await KeycloakManager.loginPKCE({
|
|
553
|
+
code: req.query.code,
|
|
554
|
+
redirect_uri: 'http://localhost:3000/callback',
|
|
555
|
+
code_verifier: req.session.pkce_verifier
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**New Code (keycloak-express-middleware, RECOMMENDED):**
|
|
560
|
+
```javascript
|
|
561
|
+
const KeycloakMiddleware = require('keycloak-express-middleware');
|
|
562
|
+
|
|
563
|
+
// Configure middleware for user authentication
|
|
564
|
+
const keycloakMiddleware = new KeycloakMiddleware({
|
|
565
|
+
baseUrl: 'https://keycloak:8443',
|
|
566
|
+
realmName: 'my-realm',
|
|
567
|
+
clientId: 'my-app',
|
|
568
|
+
clientSecret: 'my-app-secret'
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Use OIDC methods from middleware
|
|
572
|
+
const pkceFlow = keycloakMiddleware.generateAuthorizationUrl({
|
|
573
|
+
redirect_uri: 'http://localhost:3000/callback'
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const tokens = await keycloakMiddleware.loginPKCE({
|
|
577
|
+
code: req.query.code,
|
|
578
|
+
redirect_uri: 'http://localhost:3000/callback',
|
|
579
|
+
code_verifier: req.session.pkce_verifier
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Step-by-Step Migration
|
|
584
|
+
|
|
585
|
+
**1. Install keycloak-express-middleware:**
|
|
586
|
+
```bash
|
|
587
|
+
npm install keycloak-express-middleware@6.1.0
|
|
588
|
+
npm uninstall keycloak-api-manager # if no longer needed for admin operations
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**2. Initialize middleware instead of manager:**
|
|
592
|
+
```javascript
|
|
593
|
+
// Replace this:
|
|
594
|
+
// const KeycloakManager = require('keycloak-api-manager');
|
|
595
|
+
// await KeycloakManager.configure({...});
|
|
596
|
+
|
|
597
|
+
// With this:
|
|
598
|
+
const KeycloakMiddleware = require('keycloak-express-middleware');
|
|
599
|
+
const keycloakMiddleware = new KeycloakMiddleware({
|
|
600
|
+
baseUrl: process.env.KEYCLOAK_URL,
|
|
601
|
+
realmName: process.env.KEYCLOAK_REALM,
|
|
602
|
+
clientId: process.env.KEYCLOAK_CLIENT_ID,
|
|
603
|
+
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET
|
|
604
|
+
});
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**3. Replace method calls:**
|
|
608
|
+
```javascript
|
|
609
|
+
// Old (keycloak-api-manager)
|
|
610
|
+
const pkceFlow = KeycloakManager.generateAuthorizationUrl({...});
|
|
611
|
+
const tokens = await KeycloakManager.loginPKCE({...});
|
|
612
|
+
const newTokens = await KeycloakManager.login({...});
|
|
613
|
+
|
|
614
|
+
// New (keycloak-express-middleware)
|
|
615
|
+
const pkceFlow = keycloakMiddleware.generateAuthorizationUrl({...});
|
|
616
|
+
const tokens = await keycloakMiddleware.loginPKCE({...});
|
|
617
|
+
const newTokens = await keycloakMiddleware.login({...});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**4. Keep using keycloak-api-manager for admin operations (unchanged):**
|
|
621
|
+
```javascript
|
|
622
|
+
const KeycloakManager = require('keycloak-api-manager');
|
|
623
|
+
|
|
624
|
+
await KeycloakManager.configure({
|
|
625
|
+
baseUrl: 'https://keycloak:8443',
|
|
626
|
+
realmName: 'master',
|
|
627
|
+
clientId: 'admin-cli',
|
|
628
|
+
username: 'admin',
|
|
629
|
+
password: 'admin'
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Admin operations still work the same
|
|
633
|
+
const users = await KeycloakManager.users.find();
|
|
634
|
+
const realms = await KeycloakManager.realms.find();
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### API Comparison
|
|
638
|
+
|
|
639
|
+
| Operation | keycloak-api-manager (Deprecated) | keycloak-express-middleware (Recommended) |
|
|
640
|
+
|-----------|-----------------------------------|------------------------------------------|
|
|
641
|
+
| Generate PKCE URL | `KeycloakManager.generateAuthorizationUrl()` | `middleware.generateAuthorizationUrl()` |
|
|
642
|
+
| Login PKCE | `KeycloakManager.loginPKCE()` | `middleware.loginPKCE()` |
|
|
643
|
+
| Token Grant | `KeycloakManager.login()` | `middleware.login()` |
|
|
644
|
+
| Deprecated Alias | `KeycloakManager.auth()` | *(Use login()*) |
|
|
645
|
+
|
|
646
|
+
### Additional Resources
|
|
647
|
+
|
|
648
|
+
- **Middleware Documentation:** https://github.com/smartenv-crs4/keycloak-express-middleware
|
|
649
|
+
- **Migration Guide:** https://github.com/smartenv-crs4/keycloak-api-manager/blob/main/OIDC_MIGRATION_PLAN.md
|
|
650
|
+
- **Middleware Integration Report:** See keycloak-express-middleware/DETAILED_INTEGRATION_REPORT.md
|
|
651
|
+
|
|
652
|
+
### Deprecation Timeline
|
|
653
|
+
|
|
654
|
+
| Version | Status | Notes |
|
|
655
|
+
|---------|--------|-------|
|
|
656
|
+
| v5.0.8 | Supported | Last version with working OIDC methods |
|
|
657
|
+
| v6.0.0 | Deprecated | Methods work but marked @deprecated |
|
|
658
|
+
| v7.0.0 | Removed | OIDC methods will be permanently removed |
|
|
659
|
+
|
|
660
|
+
**Action Required:** Migrate to keycloak-express-middleware before v7.0.0 is released.
|
|
661
|
+
|
|
662
|
+
## Related Documentation
|
|
663
|
+
|
|
664
|
+
- [loginPKCE() API Reference](../api/configuration.md#loginpkce)
|
|
665
|
+
- [login() API Reference](../api/configuration.md#login)
|
|
666
|
+
- [OAuth2 PKCE Specification](https://tools.ietf.org/html/rfc7636)
|
|
667
|
+
- [Keycloak Authorization Code Flow](https://www.keycloak.org/docs/latest/server_admin/#_oidc)
|
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": "
|
|
3
|
+
"version": "6.0.1",
|
|
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": {
|
package/test-output.log
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> keycloak-api-manager@4.1.0 test
|
|
3
|
-
> npm --prefix test install && npm --prefix test test
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
up to date, audited 260 packages in 2s
|
|
7
|
-
|
|
8
|
-
48 packages are looking for funding
|
|
9
|
-
run `npm fund` for details
|
|
10
|
-
|
|
11
|
-
12 vulnerabilities (3 low, 3 moderate, 4 high, 2 critical)
|
|
12
|
-
|
|
13
|
-
To address all issues possible (including breaking changes), run:
|
|
14
|
-
npm audit fix --force
|
|
15
|
-
|
|
16
|
-
Some issues need review, and may require choosing
|
|
17
|
-
a different dependency.
|
|
18
|
-
|
|
19
|
-
Run `npm audit` for details.
|
|
20
|
-
|
|
21
|
-
> keycloak-api-manager-tests@1.0.0 test
|
|
22
|
-
> NODE_ENV=test NODE_PATH=./node_modules mocha --exit
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Exception during run: /Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/Handlers/userProfileHandler.js:79
|
|
26
|
-
body: JSON.strasync function(filter) {
|
|
27
|
-
^^^^^^^^
|
|
28
|
-
|
|
29
|
-
SyntaxError: Unexpected token 'function'
|
|
30
|
-
at wrapSafe (node:internal/modules/cjs/loader:1515:18)
|
|
31
|
-
at Module._compile (node:internal/modules/cjs/loader:1537:20)
|
|
32
|
-
at Object..js (node:internal/modules/cjs/loader:1708:10)
|
|
33
|
-
at Module.load (node:internal/modules/cjs/loader:1318:32)
|
|
34
|
-
at Function._load (node:internal/modules/cjs/loader:1128:12)
|
|
35
|
-
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
|
|
36
|
-
at wrapModuleLoad (node:internal/modules/cjs/loader:219:24)
|
|
37
|
-
at Module.require (node:internal/modules/cjs/loader:1340:12)
|
|
38
|
-
at require (node:internal/modules/helpers:138:16)
|
|
39
|
-
at Object.<anonymous> (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/index.js:15:24)
|
|
40
|
-
at Module._compile (node:internal/modules/cjs/loader:1565:14)
|
|
41
|
-
at Object..js (node:internal/modules/cjs/loader:1708:10)
|
|
42
|
-
at Module.load (node:internal/modules/cjs/loader:1318:32)
|
|
43
|
-
at Function._load (node:internal/modules/cjs/loader:1128:12)
|
|
44
|
-
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
|
|
45
|
-
at wrapModuleLoad (node:internal/modules/cjs/loader:219:24)
|
|
46
|
-
at Module.require (node:internal/modules/cjs/loader:1340:12)
|
|
47
|
-
at require (node:internal/modules/helpers:138:16)
|
|
48
|
-
at Object.<anonymous> (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/enableServerFeatures.js:47:25)
|
|
49
|
-
at Module._compile (node:internal/modules/cjs/loader:1565:14)
|
|
50
|
-
at Object..js (node:internal/modules/cjs/loader:1708:10)
|
|
51
|
-
at Module.load (node:internal/modules/cjs/loader:1318:32)
|
|
52
|
-
at Function._load (node:internal/modules/cjs/loader:1128:12)
|
|
53
|
-
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
|
|
54
|
-
at wrapModuleLoad (node:internal/modules/cjs/loader:219:24)
|
|
55
|
-
at Module.require (node:internal/modules/cjs/loader:1340:12)
|
|
56
|
-
at require (node:internal/modules/helpers:138:16)
|
|
57
|
-
at Object.<anonymous> (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/setup.js:29:30)
|
|
58
|
-
at Module._compile (node:internal/modules/cjs/loader:1565:14)
|
|
59
|
-
at Object..js (node:internal/modules/cjs/loader:1708:10)
|
|
60
|
-
at Module.load (node:internal/modules/cjs/loader:1318:32)
|
|
61
|
-
at Function._load (node:internal/modules/cjs/loader:1128:12)
|
|
62
|
-
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
|
|
63
|
-
at wrapModuleLoad (node:internal/modules/cjs/loader:219:24)
|
|
64
|
-
at cjsLoader (node:internal/modules/esm/translators:263:5)
|
|
65
|
-
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:196:7)
|
|
66
|
-
at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
|
|
67
|
-
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26)
|
|
68
|
-
at async formattedImport (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/node_modules/mocha/lib/nodejs/esm-utils.js:9:14)
|
|
69
|
-
at async exports.requireOrImport (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/node_modules/mocha/lib/nodejs/esm-utils.js:42:28)
|
|
70
|
-
at async exports.loadFilesAsync (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/node_modules/mocha/lib/nodejs/esm-utils.js:100:20)
|
|
71
|
-
at async singleRun (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/node_modules/mocha/lib/cli/run-helpers.js:162:3)
|
|
72
|
-
at async exports.handler (/Users/Alessandro/Src/WorkSpace/WorkspaceDemo/Idealia/Keyclock/keycloak-api-manager/test/node_modules/mocha/lib/cli/run.js:375:5)
|