UNPKG

12.5 kBMarkdownView Raw
1# hapi-auth-keycloak
2#### JSON Web Token based Authentication powered by Keycloak
3
4[![Travis](https://img.shields.io/travis/felixheck/wurst.svg)](https://travis-ci.org/felixheck/hapi-auth-keycloak/builds/) ![node](https://img.shields.io/node/v/hapi-auth-keycloak.svg) ![npm](https://img.shields.io/npm/dt/hapi-auth-keycloak.svg) [![standard](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](http://standardjs.com/) ![npm](https://img.shields.io/npm/l/hapi-auth-keycloak.svg) [![Coverage Status](https://coveralls.io/repos/github/felixheck/hapi-auth-keycloak/badge.svg?branch=master)](https://coveralls.io/github/felixheck/hapi-auth-keycloak?branch=master)
5---
6
71. [Introduction](#introduction)
82. [Installation](#installation)
93. [Usage](#usage)
104. [API](#api)
115. [Example](#example)
126. [Developing and Testing](#developing-and-testing)
137. [Contribution](#contribution)
14
15---
16
17## 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 even though the user data don't get changed until the token expires. Furthermore it is possible to enable an api key interceptor proxying the request to an api key service which returns the temporary bearer token. 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.
19
20The modules [`standard`][standardjs] and [`ava`][avajs] are used to grant a high quality implementation.
21
22#### Compatibility
23| Major Release | [hapi.js](https://github.com/hapijs/hapi) version | node version |
24| --- | --- | --- |
25| `v4.1` | `>=18.3.1 @hapi/hapi` | `>=8` |
26| `v4` | `>=18 hapi` | `>=8` |
27| `v3` | `>=17 hapi` | `>=8` |
28| `v2` | `>=12 hapi` | `>=6` |
29
30## Installation
31For installation use [npm][npm]:
32```
33$ npm install --save hapi-auth-keycloak
34```
35
36or clone the repository:
37```
38$ git clone https://github.com/felixheck/hapi-auth-keycloak
39```
40
41## Usage
42#### Import
43First you have to import the module:
44``` js
45const authKeycloak = require('hapi-auth-keycloak');
46```
47
48#### Create hapi server
49Afterwards create your hapi server if not already done:
50``` js
51const hapi = require('@hapi/hapi');
52
53const server = hapi.server({ port: 8888 });
54```
55
56#### Registration
57Finally register the plugin, set the correct options and the authentication strategy:
58``` js
59await 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});
69
70server.auth.strategy('keycloak-jwt', 'keycloak-jwt');
71```
72
73#### Route Configuration & Scope
74Define 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]:
75
76- To secure an endpoint with a resource's role , use the role name (e.g. `editor`).
77- To secure an endpoint with another resource's role, prefix the role name (e.g. `other-resource:creator`)
78- To secure an endpoint with a realm role, prefix the role name with `realm:` (e.g. `realm:admin`).
79- To secure an endpoint with [fine-grained scope definitions][rpt], prefix the Keycloak scopes with `scope:` (e.g. `scope:foo.READ`).
80
81``` js
82server.route([
83 {
84 method: 'GET',
85 path: '/',
86 config: {
87 description: 'protected endpoint',
88 auth: {
89 strategies: ['keycloak-jwt'],
90 access: {
91 scope: ['realm:admin', 'editor', 'other-resource:creator', 'scope:foo.READ']
92 }
93 },
94 handler () {
95 return 'hello world';
96 }
97 }
98 },
99]);
100```
101
102## API
103#### Plugin Options
104
105> 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.
106>
107> | Strategies | Online* | Live** |[Scopes][rpt] | Truthy Option | Note |
108> |:-----------|:------:|:----:|:-------------:|:---------------|:-------------|
109> | (1) + (2) | | | | `publicKey` | fast |
110> | (1) + (2) | x | | | | flexible |
111> | (1) | x | x | | `secret` | accurate |
112> | (1) + (2) | x | x | x | `entitlement` | fine-grained |
113>
114> **\***: Plugin interacts with the Keycloak API<br/>
115> **\*\***: Plugin validates token with help of the Keycloak API<br/>
116>
117> Please mind that the accurate strategy is 4-5x faster than the fine-grained one.<br/>
118> **Hint:** If you define neither `secret` nor `public` nor `entitlement`, the plugin retrieves the public key itself from `{realmUrl}/protocol/openid-connect/certs`.
119
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`.
123
124- `realmUrl {string}` – The absolute uri of the Keycloak realm.<br/>
125Required. Example: `https://localhost:8080/auth/realms/testme`<br/>
126
127- `clientId {string}` – The identifier of the Keycloak client/application.<br/>
128Required. Example: `foobar`<br/>
129
130- `secret {string}` – The related secret of the Keycloak client/application.<br/>
131Defining this option enables the traditional method described in the OAuth2 specification and performs an [introspect][introspect] request.<br/>
132Optional. Example: `1234-bar-4321-foo`<br/>
133
134- `publicKey {string|Buffer|Object}` – The realm its public key related to the private key used to sign the token.<br/>
135Defining this option enables the offline and non-live validation. The public key has to be in [PEM][pem] (`{string|Buffer}`) or [JWK][jwk] (`{Object}`) format. Algorithm has to be `RSA-SHA256` compatible.<br/>
136Optional.
137
138- `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/>
139Optional. Default: `undefined`.
140
141- `minTimeBetweenJwksRequests {number}` – The minimum time between JWKS requests in seconds.<br/>
142This is relevant for the online/non-live strategy retrieving JWKS from the Keycloak server.<br/>
143The value have to be a positive integer.<br/>
144Optional. Default: `0`.
145
146- `userInfo {Array.<?string>}` — List of properties which should be included in the `request.auth.credentials` object besides `scope` and `sub`.<br/>
147Optional. Default: `[]`.
148
149- `cache {Object|boolean}` — The configuration of the [hapi.js cache][hapi-server-cache] powered by [catbox][catbox]. If the property `exp` ('expires at') is undefined, the plugin uses 60 seconds as default TTL. Otherwise the cache entry expires as soon as the token itself expires.<br/>
150Please mind that an enabled cache leads to disabled live validation after the related token is cached once.<br/>
151If `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/>
152Optional. Default: `false`.
153
154- `apiKey {Object}` — The options object enabling an api key service as middleware<br/>
155Optional. 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})`
178- `field {string}` — The `Bearer` field, including the scheme (`bearer`) itself.<br/>
179Example: `bearer 12345.abcde.67890`.<br/>
180Required.
181
182If an error occurs, it gets thrown — so take care and implement a kind of catching.<br/>
183If the token is invalid, the `result` is `false`. Otherwise it is an object containing all relevant credentials.
184
185## Example
186#### `routes.js`
187
188``` js
189async function register (server, options) {
190 server.route([
191 {
192 method: 'GET',
193 path: '/',
194 config: {
195 auth: {
196 strategies: ['keycloak-jwt'],
197 access: {
198 scope: ['realm:admin', 'editor', 'other-resource:creator', 'scope:foo.READ']
199 }
200 },
201 handler (req, reply) {
202 reply(req.auth.credentials);
203 }
204 }
205 }
206 ]);
207}
208
209module.exports = {
210 register,
211 name: 'example-routes',
212 version: '0.0.1'
213};
214```
215
216#### `index.js`
217``` js
218const hapi = require('@hapi/hapi');
219const authKeycloak = require('hapi-auth-keycloak');
220const routes = require('./routes');
221
222const server = hapi.server({ port: 3000 });
223
224const options = {
225 realmUrl: 'https://localhost:8080/auth/realms/testme',
226 clientId: 'foobar',
227 minTimeBetweenJwksRequests: 15,
228 cache: true,
229 userInfo: ['name', 'email']
230};
231
232process.on('SIGINT', async () => {
233 try { 
234 await server.stop();
235 } catch (err) {
236 process.exit(err ? 1 : 0);
237 }
238});
239
240(async () => {
241 try {
242 await server.register({ plugin: authKeycloak, options });
243 server.auth.strategy('keycloak-jwt', 'keycloak-jwt');
244 await server.register({ plugin: routes });
245 await server.start();
246 console.log('Server started successfully');
247 } catch (err) {
248 console.error(err);
249 }
250})();
251```
252
253## Developing and Testing
254First you have to install all dependencies:
255```
256$ npm install
257```
258
259To execute all unit tests once, use:
260```
261$ npm test
262```
263
264or to run tests based on file watcher, use:
265```
266$ npm start
267```
268
269To get information about the test coverage, use:
270```
271$ npm run coverage
272```
273
274## Contribution
275Fork this repository and push in your ideas.
276
277Do not forget to add corresponding tests to keep up 100% test coverage.<br/>
278For further information read the [contributing guideline](CONTRIBUTING.md).
279
280[keycloak]: http://www.keycloak.org/
281[keycloak-node]: https://keycloak.gitbooks.io/documentation/content/securing_apps/topics/oidc/nodejs-adapter.html
282[hapijs]: https://hapijs.com/
283[avajs]: https://github.com/avajs/ava
284[standardjs]: https://standardjs.com/
285[babel]: https://babeljs.io/
286[npm]: https://github.com/npm/npm
287[jwt]: https://jwt.io/
288[catbox]: https://github.com/hapijs/catbox
289[bearer]: https://tools.ietf.org/html/rfc6750
290[hapi-server-cache]: https://hapijs.com/api#-servercacheoptions
291[hapi-route-options]: https://hapijs.com/api#route-options
292[jwk]: https://tools.ietf.org/html/rfc7517
293[pem]: https://tools.ietf.org/html/rfc1421
294[client-auth]: https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/client-authentication.html
295[introspect]: https://www.keycloak.org/docs/3.2/authorization_services/topics/service/protection/token-introspection.html
296[rpt]: https://www.keycloak.org/docs/3.2/authorization_services/topics/service/entitlement/entitlement-api-aapi.html
297[rpt-terms]: https://www.keycloak.org/docs/3.2/authorization_services/topics/overview/terminology.html
298[got]: https://github.com/sindresorhus/got
299[pupa]: https://github.com/sindresorhus/pupa