Skip to content

Commit 8bddb56

Browse files
authored
Merge pull request #7 from felixheck/release/2.1.0
Release/2.1.0
2 parents 9c4e336 + 4b04b3a commit 8bddb56

25 files changed

+8801
-6530
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### custom ###
22
.idea
33
.vscode
4+
.privates
45

56
### Node ###
67
# Logs

README.md

Lines changed: 83 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
---
1616

1717
## Introduction
18-
**hapi-auth-keycloak** is a plugin for [hapi.js][hapijs] which enables to protect your endpoints in a smart but professional manner using [Keycloak][keycloak] as authentication service. It is inspired by the related [express.js middleware][keycloak-node]. The plugin validates the passed [`Bearer` token][bearer] offline with a provided public key or online with help of the [Keycloak][keycloak] server. Optionally, the successfully validated tokens and the related user data get cached using [`catbox`][catbox]. The caching enables a fast processing although the user data don't get changed until the token expires. It plays well with the [hapi.js][hapijs]-integrated [authentication feature][hapi-route-options]. Besides the authentication strategy it is possible to validate tokens by yourself, e.g. to authenticate incoming websocket or queue messages.
18+
**hapi-auth-keycloak** is a plugin for [hapi.js][hapijs] which enables to protect your endpoints in a smart but professional manner using [Keycloak][keycloak] as authentication service. It is inspired by the related [express.js middleware][keycloak-node]. The plugin validates the passed [`Bearer` token][bearer] offline with a provided public key or online with help of the [Keycloak][keycloak] server. Optionally, the successfully validated tokens and the related user data get cached using [`catbox`][catbox]. The caching enables a fast processing although the user data don't get changed until the token expires. It plays well with the [hapi.js][hapijs]-integrated [authentication/authorization feature][hapi-route-options]. Besides the authentication strategy it is possible to validate tokens by yourself, e.g. to authenticate incoming websocket or queue messages.
1919

2020
This plugin is implemented in ECMAScript 6 without any transpilers like [`babel`][babel].<br/>
2121
Additionally [`standard`][standardjs] and [`ava`][avajs] are used to grant a high quality implementation.<br/>
@@ -62,9 +62,8 @@ server.register({
6262
options: {
6363
realmUrl: 'https://localhost:8080/auth/realms/testme',
6464
clientId: 'foobar',
65-
secret: '1234-bar-4321-foo',
6665
minTimeBetweenJwksRequests: 15,
67-
cache: {},
66+
cache: true,
6867
userInfo: ['name', 'email']
6968
}
7069
}, function(err) {
@@ -79,9 +78,10 @@ server.register({
7978
#### Route Configuration & Scope
8079
Define your routes and add `keycloak-jwt` when necessary. It is possible to define the necessary scope like documented by the [express.js middleware][keycloak-node]:
8180

82-
- To secure a resource with an application role for the current app, use the role name (e.g. `editor`).
83-
- To secure a resource with an application role for a different app, prefix the role name (e.g. `other-app:creator`)
84-
- To secure a resource with a realm role, prefix the role name with `realm:` (e.g. `realm:admin`).
81+
- To secure an endpoint with a resource's role , use the role name (e.g. `editor`).
82+
- To secure an endpoint with another resource's role, prefix the role name (e.g. `other-resource:creator`)
83+
- To secure an endpoint with a realm role, prefix the role name with `realm:` (e.g. `realm:admin`).
84+
- To secure an endpoint with [fine-grained scope definitions][rpt], prefix the Keycloak scopes with `scope:` (e.g. `scope:foo.READ`).
8585

8686
``` js
8787
server.route([
@@ -93,7 +93,7 @@ server.route([
9393
auth: {
9494
strategies: ['keycloak-jwt'],
9595
access: {
96-
scope: ['realm:admin', 'editor', 'other-app:creator']
96+
scope: ['realm:admin', 'editor', 'other-resource:creator', 'scope:foo.READ']
9797
}
9898
},
9999
handler (req, reply) {
@@ -107,13 +107,17 @@ server.route([
107107
## API
108108
#### Plugin Options
109109

110-
> By default, the Keycloak server has built-in [two ways to authenticate][client-auth] the client: client ID and client secret, or with a signed JWT. This plugin supports both. Check the description of `secret` and `publicKey` for further information. If the signed JWTs are used as online strategy, ensure that the identifier of the related realm key is included in their header as `kid`.
110+
> 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.
111111
>
112-
> | Strategy | Online | Option |
113-
> |:------------|:------:|:------------|
114-
> | ID + Secret | x | `secret` |
115-
> | Signed JWT | x | |
116-
> | Signed JWT | | `publicKey` |
112+
> | Strategies | Online | Live |[Scopes][rpt] | Truthy Option | Note |
113+
> |:-----------|:------:|:----:|:-------------:|:---------------|:-------------|
114+
> | (1) + (2) | | | | `publicKey` | fast |
115+
> | (1) + (2) | x | | | | flexible |
116+
> | (1) | x | x | | `secret` | accurate |
117+
> | (1) + (2) | x | x | x | `entitlement` | fine-grained |
118+
>
119+
> Please mind that the accurate strategy is 4-5x faster than the fine-grained one.<br/>
120+
> **Hint:** If you define neither `secret` nor `public` nor `entitlement`, the plugin retrieves the public key itself from `{realmUrl}/protocol/openid-connect/certs`.
117121
118122
- `realmUrl {string}` – The absolute uri of the Keycloak realm.<br/>
119123
Required. Example: `https://localhost:8080/auth/realms/testme`<br/>
@@ -122,24 +126,28 @@ Required. Example: `https://localhost:8080/auth/realms/testme`<br/>
122126
Required. Example: `foobar`<br/>
123127

124128
- `secret {string}` – The related secret of the Keycloak client/application.<br/>
125-
Defining this option enables the traditional method described in the OAuth2 specification. To perform an almost offline validation enable the cache — a simple offline verfication with symmetric keys is not provided for security reasons.<br/>
129+
Defining this option enables the traditional method described in the OAuth2 specification and performs an [introspect][introspect] request.<br/>
126130
Optional. Example: `1234-bar-4321-foo`<br/>
127-
128-
- `publicKey {string}` – The related public key of the Keycloak client/application.<br/>
129-
Defining this option enables the offline validation using signed JWTs. The public key has to be in [PEM][pem] or [JWK][jwk] format. If you define neither `secret` nor `public` key, the plugin assumes that a signed JWT has to be validated – it retrieves the public key itself from `{realmUrl}/protocol/openid-connect/certs`. The offline strategy its performance is higher but the online strategy is the most flexible one.<br/>
130-
Optional.
131+
132+
- `publicKey {string}` – The realm its public key related to the private key used to sign the token.<br/>
133+
Defining this option enables the offline and non-live validation. The public key has to be in [PEM][pem] or [JWK][jwk] format.<br/>
134+
Optional.
135+
136+
- `entitlement {boolean=true}` – The token should be validated with the entitlement API to enable fine-grained authorization. Enabling this option decelerates the process marginally. Mind that `false` is an invalid value.<br/>
137+
Optional. Default: `undefined`.
131138

132139
- `minTimeBetweenJwksRequests {number}` – The minimum time between JWKS requests in seconds.<br/>
140+
This is relevant for the online/non-live strategy retrieving JWKS from the Keycloak server.<br/>
133141
The value have to be a positive integer.<br/>
134142
Optional. Default: `0`.
135143

136-
- `cache {Object|boolean}` — The configuration of the [hapi.js cache](https://hapijs.com/api#servercacheoptions) powered by [catbox][catbox].<br/>
137-
If `false` the cache is disabled. Use `true` or an empty object (`{}`) to use the built-in default cache.<br/>
138-
Optional. Default: `false`.
139-
140144
- `userInfo {Array.<?string>}` — List of properties which should be included in the `request.auth.credentials` object besides `scope` and `sub`.<br/>
141145
Optional. Default: `[]`.<br/>
142146

147+
- `cache {Object|boolean}` — The configuration of the [hapi.js cache](https://hapijs.com/api#servercacheoptions) powered by [catbox][catbox]. If the property `iat` (issued at) or `exp` (expiresAt) is undefined, the plugin uses 60 seconds as default TTL.<br/>
148+
If `false` the cache is disabled. Use `true` or an empty object (`{}`) to use the built-in default cache.<br/>
149+
Optional. Default: `false`.
150+
143151
#### `server.kjwt.validate(field {string}, done {Function})`
144152
- `field {string}` — The `Bearer` field, including the scheme (`bearer`) itself.<br/>
145153
Example: `bearer 12345.abcde.67890`.<br/>
@@ -149,54 +157,67 @@ Required.
149157
Required.
150158

151159
## Example
160+
#### `routes.js`
152161

153162
``` js
154-
const Hapi = require('hapi');
155-
const authKeycloak = require('hapi-auth-keycloak');
163+
exports.register = function (server, options, next) {
164+
server.route([
165+
{
166+
method: 'GET',
167+
path: '/',
168+
config: {
169+
auth: {
170+
strategies: ['keycloak-jwt'],
171+
access: {
172+
scope: ['realm:admin', 'editor', 'other-resource:creator', 'scope:foo.READ']
173+
}
174+
},
175+
handler (req, reply) {
176+
reply(req.auth.credentials)
177+
}
178+
}
179+
}
180+
])
181+
182+
next()
183+
}
184+
185+
exports.register.attributes = {
186+
name: 'example-routes',
187+
version: '0.0.1'
188+
}
189+
```
190+
191+
#### `index.js`
192+
``` js
193+
const Hapi = require('hapi')
194+
const authKeycloak = require('hapi-auth-keycloak')
195+
const routes = require('./routes')
156196

157197
const server = new Hapi.Server()
158198
server.connection({ port: 3000, host: 'localhost' })
159199

160-
server.route([
161-
{
162-
method: 'GET',
163-
path: '/',
164-
config: {
165-
description: 'protected endpoint',
166-
auth: {
167-
strategies: ['keycloak-jwt'],
168-
access: {
169-
scope: ['realm:admin', 'editor', 'other-app:creator']
170-
}
171-
},
172-
handler (req, reply) {
173-
reply('hello world')
174-
}
175-
}
176-
},
177-
])
200+
const options = {
201+
realmUrl: 'https://localhost:8080/auth/realms/testme',
202+
clientId: 'foobar',
203+
minTimeBetweenJwksRequests: 15,
204+
cache: true,
205+
userInfo: ['name', 'email']
206+
}
178207

179208
process.on('SIGINT', () => {
180-
server.stop().then((err) => {
181-
process.exit((err) ? 1 : 0)
182-
})
209+
server.stop().then((err) => process.exit(err ? 1 : 0))
183210
})
184211

185-
server.register({
186-
register: authKeycloak,
187-
options: {
188-
realmUrl: 'https://localhost:8080/auth/realms/testme',
189-
clientId: 'foobar',
190-
secret: '1234-bar-4321-foo',
191-
minTimeBetweenJwksRequests: 15,
192-
cache: {},
193-
userInfo: ['name', 'email']
194-
}
195-
}).then(() => {
196-
server.auth.strategy('keycloak-jwt', 'keycloak-jwt');
212+
server.register({ register: authKeycloak, options }).then(() => {
213+
server.auth.strategy('keycloak-jwt', 'keycloak-jwt')
214+
}).then(() => (
215+
server.register({ register: routes })
216+
)).then(() => (
197217
server.start()
198-
})
199-
.catch(console.error)
218+
)).then(() => {
219+
console.log('Server started successfully')
220+
}).catch(console.error)
200221
```
201222

202223
## Developing and Testing
@@ -241,3 +262,6 @@ For further information read the [contributing guideline](CONTRIBUTING.md).
241262
[jwk]: https://tools.ietf.org/html/rfc7517
242263
[pem]: https://tools.ietf.org/html/rfc1421
243264
[client-auth]: https://keycloak.gitbooks.io/documentation/securing_apps/topics/oidc/java/client-authentication.html
265+
[introspect]: http://www.keycloak.org/docs/2.4/authorization_services_guide/topics/service/protection/token-introspection.html
266+
[rpt]: http://www.keycloak.org/docs/2.4/authorization_services_guide/topics/service/entitlement/entitlement-api-aapi.html
267+
[rpt-terms]: http://www.keycloak.org/docs/2.4/authorization_services_guide/topics/overview/terminology.html

0 commit comments

Comments
 (0)