Skip to content

Commit 84fa739

Browse files
authored
Merge pull request #22 from felixheck/multi-strategy
Fix Multi-Tenant Approach
2 parents 2dcb646 + 9e21ebc commit 84fa739

19 files changed

+671
-401
lines changed

README.md

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
3. [Usage](#usage)
1010
4. [API](#api)
1111
5. [Example](#example)
12-
6. [Developing and Testing](#developing-and-testing)
13-
7. [Contribution](#contribution)
12+
6. [Migration Guides](#migration-guides)
13+
7. [Developing and Testing](#developing-and-testing)
14+
8. [Contribution](#contribution)
1415

1516
---
1617

@@ -56,18 +57,15 @@ const server = hapi.server({ port: 8888 });
5657
#### Registration
5758
Finally register the plugin, set the correct options and the authentication strategy:
5859
``` js
59-
await server.register({
60-
plugin: authKeycloak,
61-
options: {
62-
realmUrl: 'https://localhost:8080/auth/realms/testme',
63-
clientId: 'foobar',
64-
minTimeBetweenJwksRequests: 15,
65-
cache: true,
66-
userInfo: ['name', 'email']
67-
}
68-
});
60+
await server.register({ plugin: authKeycloak });
6961

70-
server.auth.strategy('keycloak-jwt', 'keycloak-jwt');
62+
server.auth.strategy('keycloak-jwt', 'keycloak-jwt', {
63+
realmUrl: 'https://localhost:8080/auth/realms/testme',
64+
clientId: 'foobar',
65+
minTimeBetweenJwksRequests: 15,
66+
cache: true,
67+
userInfo: ['name', 'email']
68+
});
7169
```
7270

7371
#### Route Configuration & Scope
@@ -101,7 +99,30 @@ server.route([
10199

102100
## API
103101
#### Plugin Options
102+
- `apiKey {Object}` — The options object enabling an api key service as middleware<br/>
103+
Optional. Default: `undefined`.
104+
105+
- `url {string}` — The absolute url to be requested. It's possible to use a [`pupa` template][pupa] with placeholders called `realm` and `clientId` getting rendered based on the passed plugin-related options.<br/>
106+
Example: `http://barfoo.com/foo/{clientId}`<br/>
107+
Required.
108+
109+
- `in {string}` — Whether the api key is placed in the headers or query.<br/>
110+
Allowed values: `headers` & `query`<br/>
111+
Optional. Default: `headers`.
112+
113+
- `name {string}` — The name of the related headers field or query key.<br/>
114+
Optional. Default: `authorization`.
115+
116+
- `prefix {string}` — An optional prefix of the related api key value. Mind a trailing space if necessary.<br/>
117+
Optional. Default: `Api-Key `.
104118

119+
- `tokenPath {string}` — The path to the access token in the response its body as dot notation.<br/>
120+
Optional. Default: `access_token`.
121+
122+
- `request {Object}` – The detailed request options for [`got`][got].<br/>
123+
Optional. Default: `{}`
124+
125+
#### Plugin + Strategy Options
105126
> By default, the Keycloak server has built-in [two ways to authenticate][client-auth] the client: client ID and client secret **(1)**, or with a signed JWT **(2)**. This plugin supports both. If a non-live strategy is used, ensure that the identifier of the related realm key is included in their header as `kid`. Check the description of `secret`/`publicKey`/`entitlement` and the [terminology][rpt-terms] for further information.
106127
>
107128
> | Strategies | Online* | Live** |[Scopes][rpt] | Truthy Option | Note |
@@ -117,9 +138,8 @@ server.route([
117138
> Please mind that the accurate strategy is 4-5x faster than the fine-grained one.<br/>
118139
> **Hint:** If you define neither `secret` nor `public` nor `entitlement`, the plugin retrieves the public key itself from `{realmUrl}/protocol/openid-connect/certs`.
119140
120-
- `schemeName {string}` — The name used for the authentication scheme of the hapi server. Optional. Default: `keycloak-jwt`.
121-
122-
- `decoratorName {string}` — The name used for the server decorator to validate the token, [see below](#await-serverdecoratorname--kjwtvalidatefield-string). Optional. Default: `kjwt`.
141+
- `name {string}` – The unique name of the strategy<br/>
142+
Required. Example `BizApps`<br/>
123143

124144
- `realmUrl {string}` – The absolute uri of the Keycloak realm.<br/>
125145
Required. Example: `https://localhost:8080/auth/realms/testme`<br/>
@@ -151,33 +171,13 @@ Please mind that an enabled cache leads to disabled live validation after the re
151171
If `false` the cache is disabled. Use `true` or an empty object (`{}`) to use the built-in default cache. Otherwise just drop in your own cache configuration.<br/>
152172
Optional. Default: `false`.
153173

154-
- `apiKey {Object}` — The options object enabling an api key service as middleware<br/>
155-
Optional. Default: `undefined`.
156-
157-
- `url {string}` — The absolute url to be requested. It's possible to use a [`pupa` template][pupa] with placeholders called `realm` and `clientId` getting rendered based on the passed options.<br/>
158-
Example: `http://barfoo.com/foo/{clientId}`<br/>
159-
Required.
160-
161-
- `in {string}` — Whether the api key is placed in the headers or query.<br/>
162-
Allowed values: `headers` & `query`<br/>
163-
Optional. Default: `headers`.
164-
165-
- `name {string}` — The name of the related headers field or query key.<br/>
166-
Optional. Default: `authorization`.
167-
168-
- `prefix {string}` — An optional prefix of the related api key value. Mind a trailing space if necessary.<br/>
169-
Optional. Default: `Api-Key `.
170-
171-
- `tokenPath {string}` — The path to the access token in the response its body as dot notation.<br/>
172-
Optional. Default: `access_token`.
173-
174-
- `request {Object}` – The detailed request options for [`got`][got].<br/>
175-
Optional. Default: `{}`
176-
177-
#### `await server[decoratorName = 'kjwt'].validate(field {string})`
174+
#### `await server.kjwt.validate(field {string}, name {string})`
178175
- `field {string}` — The `Bearer` field, including the scheme (`bearer`) itself.<br/>
179176
Example: `bearer 12345.abcde.67890`.<br/>
180177
Required.
178+
- `name {string}` — The `name` strategy option, to select the strategy to be used.<br/>
179+
Example: `BizApps`.<br/>
180+
Required.
181181

182182
If an error occurs, it gets thrown — so take care and implement a kind of catching.<br/>
183183
If the token is invalid, the `result` is `false`. Otherwise it is an object containing all relevant credentials.
@@ -221,7 +221,13 @@ const routes = require('./routes');
221221

222222
const server = hapi.server({ port: 3000 });
223223

224-
const options = {
224+
const pluginOptions = {
225+
apiKey: {
226+
url: 'http://barfoo.com/foo/foobar'
227+
}
228+
}
229+
230+
const strategyOptions = {
225231
realmUrl: 'https://localhost:8080/auth/realms/testme',
226232
clientId: 'foobar',
227233
minTimeBetweenJwksRequests: 15,
@@ -239,8 +245,11 @@ process.on('SIGINT', async () => {
239245

240246
(async () => {
241247
try {
242-
await server.register({ plugin: authKeycloak, options });
243-
server.auth.strategy('keycloak-jwt', 'keycloak-jwt');
248+
await server.register({
249+
plugin: authKeycloak,
250+
options: pluginOptions
251+
});
252+
server.auth.strategy('keycloak-jwt', 'keycloak-jwt', strategyOptions);
244253
await server.register({ plugin: routes });
245254
await server.start();
246255
console.log('Server started successfully');
@@ -250,6 +259,21 @@ process.on('SIGINT', async () => {
250259
})();
251260
```
252261

262+
## Migration Guides
263+
#### `v4.2` to `v4.3`
264+
**Features**
265+
- It's now possible to register multiple strategies with the same scheme `keycloak-jwt`
266+
267+
**Changes**
268+
- `name` is a new unique strategy-related option.
269+
- `apiKey.url` not longer accepts placeholders
270+
- The [option](#api) setup changed. All plugin-related options are used as defaults for [strategy-related options][strategy-options].
271+
- Even though every strategy-related option can also set via the plugin options, `apiKey` can only be set once in the plugin options.
272+
273+
In case of multiple registered strategies for this scheme:
274+
- Use at least a different `name` option in each strategy.
275+
- `server.kjwt.validate` requires `name` as second argument
276+
253277
## Developing and Testing
254278
First you have to install all dependencies:
255279
```
@@ -297,3 +321,4 @@ For further information read the [contributing guideline](CONTRIBUTING.md).
297321
[rpt-terms]: https://www.keycloak.org/docs/3.2/authorization_services/topics/overview/terminology.html
298322
[got]: https://github.com/sindresorhus/got
299323
[pupa]: https://github.com/sindresorhus/pupa
324+
[strategy-options]: https://hapijs.com/api#-serverauthstrategyname-scheme-options

package-lock.json

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/apiKey.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ function parseUrl (pluginOptions) {
1616
const { apiKey, clientId, realmUrl } = pluginOptions
1717

1818
return !!apiKey && pupa(apiKey.url, {
19-
realm: realmUrl.split('/').slice(-1),
20-
clientId
19+
...(realmUrl ? { realm: realmUrl.split('/').slice(-1) } : {}),
20+
...(clientId ? { clientId } : {})
2121
})
2222
}
2323

@@ -67,16 +67,15 @@ function getRequestOptions (request, options) {
6767
* @public
6868
*
6969
* Extend the hapi request life cycle with an
70-
* additional api key interceptor.
70+
* additional api key interceptor.
7171
*
7272
* @param {Hapi.server} server The related hapi server object
73-
* @param {Object} pluginOptions The plugin related options
7473
* @param {Object} options The api key related options
7574
* @param {string} url The url to be requested
7675
*
7776
* @throws {Boom.unauthorized} If requesting the access token failed
7877
*/
79-
function extendLifeCycle (server, pluginOptions, options, url) {
78+
function extendLifeCycle (server, options, url) {
8079
server.ext('onRequest', async (request, h) => {
8180
const requestOptions = getRequestOptions(request, options)
8281

@@ -91,7 +90,7 @@ function extendLifeCycle (server, pluginOptions, options, url) {
9190
throw raiseUnauthorized(
9291
errorMessages.apiKey,
9392
err.message,
94-
pluginOptions.schemeName,
93+
null,
9594
options.prefix.trim()
9695
)
9796
}
@@ -117,7 +116,7 @@ function init (server, pluginOptions) {
117116
const url = parseUrl(pluginOptions)
118117

119118
if (options) {
120-
extendLifeCycle(server, pluginOptions, options, url)
119+
extendLifeCycle(server, options, url)
121120
}
122121
}
123122

0 commit comments

Comments
 (0)