1 | # Anvil Connect client for Node.js
|
2 | [![NPM Version](https://img.shields.io/npm/v/anvil-connect-nodejs.svg?style=flat)](https://npm.im/anvil-connect-nodejs)
|
3 | [![Build Status](https://travis-ci.org/anvilresearch/connect-nodejs.svg?branch=master)](https://travis-ci.org/anvilresearch/connect-nodejs)
|
4 |
|
5 | [Anvil Connect][connect] is a modern authorization server built to authenticate
|
6 | your users and protect your APIs. It's based on [OAuth 2.0][oauth2] and
|
7 | [OpenID Connect][oidc].
|
8 |
|
9 | This library is a low level OpenID Connect and Anvil Connect API client.
|
10 | Previous versions included Express-specific functions and
|
11 | middleware. These higher-level functions are being split out into a
|
12 | [separate library][connect-express].
|
13 |
|
14 | [oauth2]: http://tools.ietf.org/html/rfc6749
|
15 | [oidc]: http://openid.net/connect/
|
16 | [connect]: https://github.com/anvilresearch/connect
|
17 | [connect-nodejs]: https://github.com/anvilresearch/connect-nodejs
|
18 | [connect-express]: https://github.com/anvilresearch/connect-express
|
19 |
|
20 | ### Install
|
21 |
|
22 | ```bash
|
23 | $ npm install anvil-connect-nodejs --save
|
24 | ```
|
25 |
|
26 | ### Configure
|
27 |
|
28 | Before performing any other operations (such as verifying or refreshing OIDC
|
29 | tokens, or accessing the AnvilConnect-specific API (such as creating users),
|
30 | an OIDC client needs to be configured and registered with the server (OIDC
|
31 | Provider, OP for short).
|
32 |
|
33 | #### new AnvilConnect(config)
|
34 |
|
35 | ```javascript
|
36 | var AnvilConnectClient = require('anvil-connect-nodejs');
|
37 |
|
38 | // If the client has been pre-registered, pass the credentials to constructor
|
39 | var client = new AnvilConnectClient({
|
40 | issuer: 'https://connect.example.com',
|
41 | client_id: 'CLIENT_ID',
|
42 | client_secret: 'CLIENT_SECRET',
|
43 | redirect_uri: 'REDIRECT_URI',
|
44 | scope: 'realm'
|
45 | })
|
46 | client.initProvider()
|
47 | .then(function () {
|
48 | // Ready to verify() tokens, refresh(), etc
|
49 | })
|
50 |
|
51 | // If the client has not been registered, use OIDC dynamic registration
|
52 | var client = new AnvilConnectClient({ issuer: 'https://connect.example.com' })
|
53 | client.initProvider()
|
54 | .then(function () {
|
55 | // Provider config loaded (.discover() and .getJWKs() called)
|
56 |
|
57 | // Ready to register()
|
58 | return client.register({
|
59 | // ... see below for registration options
|
60 | })
|
61 | })
|
62 | .then(function () {
|
63 | // Client is now registered. Ready to verify() tokens, refresh(), etc
|
64 | })
|
65 | .catch(function (err) {
|
66 | // as always, don't forget error handling
|
67 | })
|
68 | ```
|
69 |
|
70 | **options**
|
71 |
|
72 | * `issuer` – REQUIRED uri of your OpenID Connect provider
|
73 | * `client_id` – OPTIONAL client identifier issued by OIDC provider during
|
74 | registration
|
75 | * `client_secret` – OPTIONAL confidential value issued by OIDC provider during
|
76 | registration
|
77 | * `redirect_uri` – OPTIONAL uri users will be redirected back to after
|
78 | authenticating with the issuer
|
79 | * `scope` – OPTIONAL array of strings, or space delimited string value
|
80 | containing scopes to be included in authorization requests.
|
81 | Defaults to `openid profile`
|
82 |
|
83 |
|
84 | ### OpenID Connect
|
85 |
|
86 | #### client.discover()
|
87 |
|
88 | Returns a promise providing [OpenID Metadata][oidc-meta] retrieved from the
|
89 | `.well-known/openid-configuration` endpoint for the configured issuer. Sets the
|
90 | response data as `client.configuration`.
|
91 |
|
92 | [oidc-meta]: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
93 |
|
94 | **example**
|
95 |
|
96 | ```javascript
|
97 | client.discover()
|
98 | .then(function (openidMetadata) {
|
99 | // client.configuration === openidMetadata
|
100 | })
|
101 | .catch(function (error) {
|
102 | // ...
|
103 | })
|
104 | ```
|
105 |
|
106 | #### client.getJWKs()
|
107 |
|
108 | Returns a promise providing the JWK set published by the configured issuer.
|
109 | Depends on a prior call to `client.discover()`.
|
110 |
|
111 | **example**
|
112 |
|
113 | ```javascript
|
114 | client.getJWKs()
|
115 | .then(function (jwks) {
|
116 | // client.jwks === jwks
|
117 | })
|
118 | .catch(function (error) {
|
119 | // ...
|
120 | })
|
121 | ```
|
122 |
|
123 | #### client.register(registration)
|
124 |
|
125 | Dynamically registers a new client with the configured issuer and returns a
|
126 | promise for the new client registration. You can learn more about [dynamic
|
127 | registration for Anvil Connect][dynamic-registration] in the docs. Depends on a
|
128 | prior call to `client.discover()`.
|
129 |
|
130 | [dynamic-registration]: https://github.com/anvilresearch/connect-docs/blob/master/clients.md#dynamic-registration
|
131 |
|
132 | **example**
|
133 |
|
134 | ```javascript
|
135 | var options = {
|
136 | client_name: 'Antisocial Network',
|
137 | client_uri: 'https://app.example.com',
|
138 | logo_uri: 'https://app.example.com/assets/logo.png',
|
139 | response_types: ['code'],
|
140 | grant_types: ['authorization_code', 'refresh_token'],
|
141 | default_max_age: 86400, // one day in seconds
|
142 | redirect_uris: ['https://app.example.com/callback.html', 'https://app.example.com/other.html'],
|
143 | post_logout_redirect_uris: ['https://app.example.com']
|
144 | }
|
145 | client.register(options)
|
146 | .then(function (data) {
|
147 | // After the register request resolves
|
148 | // client.client_id and .client_secret are initialized
|
149 | // and the rest of the returned data is set to client.registration
|
150 | })
|
151 | ```
|
152 |
|
153 | #### client.authorizationUri([endpoint|options])
|
154 |
|
155 | Accepts a string specifying a non-default endpoint or an options object and
|
156 | returns an authorization URI. Depends on a prior call to `client.discover()` and
|
157 | `client_id` being configured.
|
158 |
|
159 | **options**
|
160 |
|
161 | * All options accepted by `client.authorizationParams()`.
|
162 | * `endpoint` – This value is used for the path in the returned URI. Defaults to `authorize`.
|
163 |
|
164 | **example**
|
165 |
|
166 | ```javascript
|
167 | client.authorizationUri()
|
168 | // 'https://connect.example.com/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=openid%20profile%20more'
|
169 |
|
170 | client.authorizationUri('signin')
|
171 | // 'https://connect.example.com/signin?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=openid%20profile%20more'
|
172 |
|
173 | client.authorizationUri({
|
174 | endpoint: 'connect/google',
|
175 | response_type: 'code id_token token',
|
176 | redirect_uri: 'OTHER_REDIRECT_URI',
|
177 | scope: 'openid profile extra'
|
178 | })
|
179 | // 'https://connect.example.com/connect/google?response_type=code%20id_token%20token&client_id=CLIENT_ID&redirect_uri=OTHER_REDIRECT_URI&scope=openid%20profile%20extra'
|
180 | ```
|
181 |
|
182 |
|
183 | #### client.authorizationParams(options)
|
184 |
|
185 | Accepts an options object and returns an object containing authorization params
|
186 | including default values. Depends on `client_id` being configured.
|
187 |
|
188 | *Note:* This is a low-level function used by `authorizationUri()`,
|
189 | documented to list its parameters (which are passed through to
|
190 | `authorizationParams()`). It's unlikely that your code will be invoking it
|
191 | directly.
|
192 |
|
193 | **options**
|
194 |
|
195 | * `response_type` – defaults to `code`
|
196 | * `redirect_uri` – defaults to the `redirect_uri` configured for this client
|
197 | * `scope` – defaults to the scope configured for this client
|
198 | * `state`
|
199 | * `response_mode`
|
200 | * `nonce`
|
201 | * `display`
|
202 | * `prompt`
|
203 | * `max_age`
|
204 | * `ui_locales`
|
205 | * `id_token_hint`
|
206 | * `login_hint`
|
207 | * `acr_values`
|
208 | * `email`
|
209 | * `password`
|
210 | * `provider`
|
211 |
|
212 | #### client.token(options)
|
213 |
|
214 | Given an authorization code is provided as the `code` option, this method will
|
215 | exchange the auth code for a set of token credentials, then verify the
|
216 | signatures and decode the payloads. Depends on `client_id` and `client_secret`
|
217 | being configured, and prior calls to `client.discover()` and `client.getJWKs()`.
|
218 |
|
219 | **options**
|
220 |
|
221 | * `code` – value obtained from a successful authorization request with `code`
|
222 | in the `response_types` request param
|
223 |
|
224 | **example**
|
225 |
|
226 | ```javascript
|
227 | client.token({ code: 'AUTHORIZATION_CODE' })
|
228 | ```
|
229 |
|
230 | #### client.refresh(options)
|
231 |
|
232 | Given an refresh_token is provided as the `refresh_token` option, this method
|
233 | will exchange the refresh_token for a set of token credentials, then verify the
|
234 | signatures. Depends on `client_id` and `client_secret` being configured, and
|
235 | prior calls to `client.discover()` and `client.getJWKs()`.
|
236 |
|
237 | **options**
|
238 |
|
239 | * `refresh_token` – value obtained from a successful authorization request with
|
240 | `token` in the `response_types` request param
|
241 |
|
242 | **example**
|
243 |
|
244 | ```javascript
|
245 | client.refresh({ refresh_token: 'REFRESH_TOKEN' })
|
246 | ```
|
247 |
|
248 | #### client.userInfo(options)
|
249 |
|
250 | Get user info from the issuer.
|
251 |
|
252 | **options**
|
253 |
|
254 | * `token` – access token
|
255 |
|
256 | **example**
|
257 |
|
258 | ```javascript
|
259 | client.userInfo({ token: 'ACCESS_TOKEN' })
|
260 | ```
|
261 |
|
262 | #### client.verify(token, options)
|
263 |
|
264 | ### Anvil Connect API
|
265 |
|
266 | #### Clients
|
267 |
|
268 | #### client.clients.list()
|
269 | #### client.clients.get(id)
|
270 | #### client.clients.create(data)
|
271 | #### client.clients.update(id, data)
|
272 | #### client.clients.delete(id)
|
273 |
|
274 | #### Roles
|
275 |
|
276 | #### client.roles.list()
|
277 | #### client.roles.get(id)
|
278 | #### client.roles.create(data)
|
279 | #### client.roles.update(id, data)
|
280 | #### client.roles.delete(id)
|
281 |
|
282 | #### Scopes
|
283 |
|
284 | #### client.scopes.list()
|
285 | #### client.scopes.get(id)
|
286 | #### client.scopes.create(data)
|
287 | #### client.scopes.update(id, data)
|
288 | #### client.scopes.delete(id)
|
289 |
|
290 | #### Users
|
291 |
|
292 | #### client.users.list()
|
293 | #### client.users.get(id)
|
294 | #### client.users.create(data)
|
295 | #### client.users.update(id, data)
|
296 | #### client.users.delete(id)
|
297 |
|
298 | ### Example
|
299 |
|
300 | ```javascript
|
301 | var AnvilConnectClient = require('anvil-connect-nodejs');
|
302 |
|
303 | // Assumes a preregistered client (if not, call register() after initProvider())
|
304 | var client = new AnvilConnectClient({
|
305 | issuer: 'https://connect.example.com',
|
306 | client_id: 'CLIENT_ID',
|
307 | client_secret: 'CLIENT_SECRET',
|
308 | redirect_uri: 'REDIRECT_URI'
|
309 | })
|
310 |
|
311 | // Initialize the provider config (endpoints and public keys)
|
312 | client.initProvider()
|
313 | .then(function () {
|
314 | // At this point, provider config and public keys are loaded and cached
|
315 | console.log(client.configuration)
|
316 | console.log(jwks)
|
317 |
|
318 | // Now, build an authorization url
|
319 | return client.authorizationUri()
|
320 | })
|
321 | .then(function (url) {
|
322 | console.log(url)
|
323 |
|
324 | // handle an authorization response
|
325 | // this verifies the signatures on tokens received from the authorization server
|
326 | return client.token({ code: 'AUTHORIZATION_CODE' })
|
327 | })
|
328 | .then(function (tokens) {
|
329 | // a successful call to tokens() gives us id_token, access_token,
|
330 | // refresh_token, expiration, and the decoded payloads of the JWTs
|
331 | console.log(tokens)
|
332 |
|
333 | // get userinfo
|
334 | return client.userInfo({ token: tokens.access_token })
|
335 | })
|
336 | .then(function (userInfo) {
|
337 | console.log(userInfo)
|
338 |
|
339 | // verify an access token received by an API service
|
340 | return client.verify(JWT, { scope: 'research' })
|
341 | })
|
342 | ```
|