keycloak-api-manager 5.0.4 → 5.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/docs/api/configuration.md +55 -0
- package/docs/api-reference.md +1 -0
- package/index.js +43 -2
- package/package.json +1 -1
- package/test/specs/clientCredentials.test.js +66 -0
package/README.md
CHANGED
|
@@ -66,11 +66,14 @@ In Keycloak 26.x, management-permissions APIs used by group/user fine-grained te
|
|
|
66
66
|
- `setConfig(overrides)`
|
|
67
67
|
- `getToken()`
|
|
68
68
|
- `login(credentials)`
|
|
69
|
+
- `loginPKCE(credentials)`
|
|
69
70
|
- `auth(credentials)`
|
|
70
71
|
- `stop()`
|
|
71
72
|
|
|
72
73
|
`login(credentials)` is the preferred OIDC token endpoint helper for login/token grant flows (user/client).
|
|
73
74
|
|
|
75
|
+
`loginPKCE(credentials)` is a specialized helper for Authorization Code + PKCE token exchange.
|
|
76
|
+
|
|
74
77
|
`auth(credentials)` is kept as backward-compatible alias and does not replace the internal admin session configured by `configure()`.
|
|
75
78
|
|
|
76
79
|
Configured handler namespaces:
|
|
@@ -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
|
+
- [loginPKCE()](#loginpkce)
|
|
11
12
|
- [auth()](#auth)
|
|
12
13
|
- [stop()](#stop)
|
|
13
14
|
|
|
@@ -347,6 +348,60 @@ console.log(refreshed.access_token);
|
|
|
347
348
|
|
|
348
349
|
---
|
|
349
350
|
|
|
351
|
+
## loginPKCE()
|
|
352
|
+
|
|
353
|
+
Perform Authorization Code + PKCE token exchange.
|
|
354
|
+
|
|
355
|
+
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
|
+
|
|
357
|
+
**Syntax:**
|
|
358
|
+
```javascript
|
|
359
|
+
await KeycloakManager.loginPKCE(credentials)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Parameters
|
|
363
|
+
|
|
364
|
+
#### credentials (Object) ⚠️ Required
|
|
365
|
+
|
|
366
|
+
| Property | Type | Required | Description |
|
|
367
|
+
|----------|------|----------|-------------|
|
|
368
|
+
| `code` | string | ⚠️ Yes | Authorization code returned by Keycloak |
|
|
369
|
+
| `redirect_uri` | string | ⚠️ Yes* | Redirect URI used in authorize request |
|
|
370
|
+
| `redirectUri` | string | ⚠️ Yes* | CamelCase alias of `redirect_uri` |
|
|
371
|
+
| `code_verifier` | string | ⚠️ Yes* | PKCE code verifier |
|
|
372
|
+
| `codeVerifier` | string | ⚠️ Yes* | CamelCase alias of `code_verifier` |
|
|
373
|
+
| `client_id` | string | 📋 Optional | Overrides runtime `clientId` |
|
|
374
|
+
| `clientId` | string | 📋 Optional | CamelCase alias of `client_id` |
|
|
375
|
+
| `client_secret` | string | 📋 Optional | Overrides runtime `clientSecret` |
|
|
376
|
+
| `clientSecret` | string | 📋 Optional | CamelCase alias of `client_secret` |
|
|
377
|
+
| `scope` | string | 📋 Optional | Additional scope string |
|
|
378
|
+
|
|
379
|
+
`*` required with either snake_case or camelCase form.
|
|
380
|
+
|
|
381
|
+
### Returns
|
|
382
|
+
|
|
383
|
+
**Promise\<Object\>** - Token payload returned by Keycloak (`access_token`, `refresh_token`, `id_token`, `expires_in`, ...)
|
|
384
|
+
|
|
385
|
+
### Example
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
const tokenResponse = await KeycloakManager.loginPKCE({
|
|
389
|
+
code: authorizationCode,
|
|
390
|
+
redirectUri: 'https://my-app.example.com/auth/callback',
|
|
391
|
+
codeVerifier: pkceCodeVerifier
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
console.log(tokenResponse.access_token);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Notes
|
|
398
|
+
|
|
399
|
+
- `loginPKCE()` forces `grant_type=authorization_code`.
|
|
400
|
+
- If `client_id/client_secret` are omitted, runtime values from `configure()` are used.
|
|
401
|
+
- `loginPKCE()` does not generate authorize URLs, `state`, or PKCE challenge; it only performs token exchange.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
350
405
|
## stop()
|
|
351
406
|
|
|
352
407
|
Stop the automatic token refresh timer and cleanup resources. Call this when shutting down your application.
|
package/docs/api-reference.md
CHANGED
|
@@ -77,6 +77,7 @@ KeycloakManager.stop();
|
|
|
77
77
|
| `setConfig()` | Runtime configuration | Core |
|
|
78
78
|
| `getToken()` | Get current access token | Core |
|
|
79
79
|
| `login()` | Preferred OIDC token grant/login endpoint wrapper | Core |
|
|
80
|
+
| `loginPKCE()` | Authorization Code + PKCE token exchange helper | Core |
|
|
80
81
|
| `auth()` | Backward-compatible alias of `login()` | Core |
|
|
81
82
|
| `stop()` | Stop token refresh timer | Core |
|
|
82
83
|
| `realms` | Realm management | realmsHandler |
|
package/index.js
CHANGED
|
@@ -148,10 +148,10 @@ async function requestOidcToken(credentials = {}) {
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
if (runtimeConfig.clientId) {
|
|
151
|
+
if (runtimeConfig.clientId && !body.has('client_id')) {
|
|
152
152
|
body.append('client_id', runtimeConfig.clientId);
|
|
153
153
|
}
|
|
154
|
-
if (runtimeConfig.clientSecret) {
|
|
154
|
+
if (runtimeConfig.clientSecret && !body.has('client_secret')) {
|
|
155
155
|
body.append('client_secret', runtimeConfig.clientSecret);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -184,3 +184,44 @@ exports.auth = async function auth(credentials = {}) {
|
|
|
184
184
|
exports.login = async function login(credentials = {}) {
|
|
185
185
|
return requestOidcToken(credentials);
|
|
186
186
|
};
|
|
187
|
+
|
|
188
|
+
exports.loginPKCE = async function loginPKCE(credentials = {}) {
|
|
189
|
+
const {
|
|
190
|
+
code,
|
|
191
|
+
redirect_uri,
|
|
192
|
+
redirectUri,
|
|
193
|
+
code_verifier,
|
|
194
|
+
codeVerifier,
|
|
195
|
+
client_id,
|
|
196
|
+
clientId,
|
|
197
|
+
client_secret,
|
|
198
|
+
clientSecret,
|
|
199
|
+
...rest
|
|
200
|
+
} = credentials;
|
|
201
|
+
|
|
202
|
+
const resolvedCode = code;
|
|
203
|
+
const resolvedRedirectUri = redirect_uri || redirectUri;
|
|
204
|
+
const resolvedCodeVerifier = code_verifier || codeVerifier;
|
|
205
|
+
const resolvedClientId = client_id || clientId;
|
|
206
|
+
const resolvedClientSecret = client_secret || clientSecret;
|
|
207
|
+
|
|
208
|
+
if (!resolvedCode) {
|
|
209
|
+
throw new Error('loginPKCE requires "code".');
|
|
210
|
+
}
|
|
211
|
+
if (!resolvedRedirectUri) {
|
|
212
|
+
throw new Error('loginPKCE requires "redirect_uri" (or "redirectUri").');
|
|
213
|
+
}
|
|
214
|
+
if (!resolvedCodeVerifier) {
|
|
215
|
+
throw new Error('loginPKCE requires "code_verifier" (or "codeVerifier").');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return requestOidcToken({
|
|
219
|
+
grant_type: 'authorization_code',
|
|
220
|
+
code: resolvedCode,
|
|
221
|
+
redirect_uri: resolvedRedirectUri,
|
|
222
|
+
code_verifier: resolvedCodeVerifier,
|
|
223
|
+
...(resolvedClientId ? { client_id: resolvedClientId } : {}),
|
|
224
|
+
...(resolvedClientSecret ? { client_secret: resolvedClientSecret } : {}),
|
|
225
|
+
...rest
|
|
226
|
+
});
|
|
227
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keycloak-api-manager",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.5",
|
|
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": {
|
|
@@ -76,4 +76,70 @@ describe('Authentication - client_credentials grant', function () {
|
|
|
76
76
|
expect(token).to.have.property('accessToken');
|
|
77
77
|
expect(token.accessToken).to.be.a('string').and.to.have.length.greaterThan(0);
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
it('supports login() for direct token grant', async function () {
|
|
81
|
+
if (!testClientId || !testClientSecret) {
|
|
82
|
+
this.skip();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
keycloakManager.setConfig({ realmName: TEST_REALM });
|
|
87
|
+
|
|
88
|
+
const tokenPayload = await keycloakManager.login({
|
|
89
|
+
grant_type: 'client_credentials',
|
|
90
|
+
client_id: testClientId,
|
|
91
|
+
client_secret: testClientSecret,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(tokenPayload).to.have.property('access_token');
|
|
95
|
+
expect(tokenPayload.access_token).to.be.a('string').and.to.have.length.greaterThan(0);
|
|
96
|
+
expect(tokenPayload).to.have.property('token_type');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('validates loginPKCE required parameters', async function () {
|
|
100
|
+
try {
|
|
101
|
+
await keycloakManager.loginPKCE({});
|
|
102
|
+
throw new Error('Expected loginPKCE to fail when code is missing');
|
|
103
|
+
} catch (error) {
|
|
104
|
+
expect(error.message).to.include('requires "code"');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await keycloakManager.loginPKCE({ code: 'dummy-code' });
|
|
109
|
+
throw new Error('Expected loginPKCE to fail when redirect_uri is missing');
|
|
110
|
+
} catch (error) {
|
|
111
|
+
expect(error.message).to.include('requires "redirect_uri"');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await keycloakManager.loginPKCE({
|
|
116
|
+
code: 'dummy-code',
|
|
117
|
+
redirect_uri: 'https://example.local/callback',
|
|
118
|
+
});
|
|
119
|
+
throw new Error('Expected loginPKCE to fail when code_verifier is missing');
|
|
120
|
+
} catch (error) {
|
|
121
|
+
expect(error.message).to.include('requires "code_verifier"');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('attempts PKCE token exchange and returns OIDC error for invalid code', async function () {
|
|
126
|
+
if (!testClientId || !testClientSecret) {
|
|
127
|
+
this.skip();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await keycloakManager.loginPKCE({
|
|
133
|
+
code: 'invalid-authorization-code',
|
|
134
|
+
redirect_uri: 'https://example.local/callback',
|
|
135
|
+
code_verifier: 'plain-verifier-for-test-only',
|
|
136
|
+
client_id: testClientId,
|
|
137
|
+
client_secret: testClientSecret,
|
|
138
|
+
});
|
|
139
|
+
throw new Error('Expected loginPKCE to fail with invalid authorization code');
|
|
140
|
+
} catch (error) {
|
|
141
|
+
expect(error).to.be.instanceOf(Error);
|
|
142
|
+
expect(error.message).to.be.a('string').and.to.have.length.greaterThan(0);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
79
145
|
});
|