Skip to content

Commit ce2bf66

Browse files
committed
feat: add tls_client_auth client authentication method
Defined in https://tools.ietf.org/html/draft-ietf-oauth-mtls-11 this client authentication method uses mutual Transport Layer Security (TLS) to authenticate a client for token, introspection and revocation endpoints. It relies on your TLS-offloading proxy to parse, validate and send metadata about the X.509 certificate, as well as status of the verification, via headers to the upstream node.js application. See the configuration doc tokenEndpointAuthMethods section for more details.
1 parent 186de0d commit ce2bf66

File tree

11 files changed

+235
-40
lines changed

11 files changed

+235
-40
lines changed

docs/configuration.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,7 @@ async audiences(ctx, sub, token, use) {
11641164

11651165
### claims
11661166

1167-
List of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.
1167+
Array of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.
11681168

11691169
_**affects**_: discovery, ID Token claim names, Userinfo claim names
11701170

@@ -1319,7 +1319,7 @@ _**affects**_: discovery
13191319

13201320
### dynamicScopes
13211321

1322-
List of the dynamic scope values that the OP supports. These must be regular expressions that the OP will check string scope values, that aren't in the static list, against.
1322+
Array of the dynamic scope values that the OP supports. These must be regular expressions that the OP will check string scope values, that aren't in the static list, against.
13231323

13241324
_**affects**_: discovery, authorization, ID Token claims, Userinfo claims
13251325

@@ -1608,7 +1608,7 @@ async interactionUrl(ctx, interaction) {
16081608

16091609
### introspectionEndpointAuthMethods
16101610

1611-
List of Client Authentication methods supported by this OP's Introspection Endpoint
1611+
Array of Client Authentication methods supported by this OP's Introspection Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods
16121612

16131613
_**affects**_: discovery, client authentication for introspection, registration and registration management
16141614

@@ -1708,7 +1708,7 @@ async postLogoutRedirectUri(ctx) {
17081708

17091709
### prompts
17101710

1711-
List of the prompt values that the OpenID Provider MAY be able to resolve
1711+
Array of the prompt values that the OpenID Provider MAY be able to resolve
17121712

17131713
_**affects**_: authorization
17141714

@@ -1762,7 +1762,7 @@ async renderError(ctx, out, error) {
17621762

17631763
### responseTypes
17641764

1765-
List of response_type values that OP supports
1765+
Array of response_type values that OP supports
17661766

17671767
_**affects**_: authorization, discovery, registration, registration management
17681768
<details>
@@ -1784,7 +1784,7 @@ _**affects**_: authorization, discovery, registration, registration management
17841784

17851785
### revocationEndpointAuthMethods
17861786

1787-
List of Client Authentication methods supported by this OP's Revocation Endpoint
1787+
Array of Client Authentication methods supported by this OP's Revocation Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods
17881788

17891789
_**affects**_: discovery, client authentication for revocation, registration and registration management
17901790

@@ -1825,7 +1825,7 @@ _**affects**_: routing
18251825

18261826
### scopes
18271827

1828-
List of the scope values that the OP supports
1828+
Array of the scope values that the OP supports
18291829

18301830
_**affects**_: discovery, authorization, ID Token claims, Userinfo claims
18311831

@@ -1836,7 +1836,7 @@ _**default value**_:
18361836

18371837
### subjectTypes
18381838

1839-
List of the Subject Identifier types that this OP supports. Valid types are
1839+
Array of the Subject Identifier types that this OP supports. Valid types are
18401840
- `public`
18411841
- `pairwise`
18421842

@@ -1849,7 +1849,7 @@ _**default value**_:
18491849

18501850
### tokenEndpointAuthMethods
18511851

1852-
List of Client Authentication methods supported by this OP's Token Endpoint
1852+
Array of Client Authentication methods supported by this OP's Token Endpoint
18531853

18541854
_**affects**_: discovery, client authentication for token endpoint, registration and registration management
18551855

@@ -1861,6 +1861,41 @@ _**default value**_:
18611861
'client_secret_post',
18621862
'private_key_jwt' ]
18631863
```
1864+
<details>
1865+
<summary>(Click to expand) Supported values list
1866+
</summary>
1867+
<br>
1868+
1869+
```js
1870+
[
1871+
'none',
1872+
'client_secret_basic', 'client_secret_post',
1873+
'client_secret_jwt', 'private_key_jwt',
1874+
'tls_client_auth',
1875+
]
1876+
```
1877+
</details>
1878+
<details>
1879+
<summary>(Click to expand) Setting up tls_client_auth</summary>
1880+
<br>
1881+
1882+
1883+
To enable `tls_client_auth` the provider expects `x-ssl-client-verify` and `x-ssl-client-s-dn` headers to be presented by your TLS-offloading proxy with the variable values set by these proxies. An important aspect is to sanitize the inbound request headers at the proxy. <br/><br/> The most common openssl based proxies are Apache and NGINX, with those you're looking to use <br/><br/> __`SSLVerifyClient` (Apache) / `ssl_verify_client` (NGINX)__ `require` - if you only support tls_client_auth, `optional` if you also support additional non-MTLS based authentication methods, `optional_no_ca` - if you also support additional non-MTLS based authentication methods AND self_signed_tls_client_auth (not implemented yet) <br/><br/> __`SSLCACertificateFile` or `SSLCACertificatePath` (Apache) / `ssl_client_certificate` (NGINX)__ with the values pointing to your accepted CA Certificates <br/><br/> Set the proxy request headers with variables set as a result of enabling MTLS
1884+
1885+
1886+
```nginx
1887+
# NGINX
1888+
proxy_set_header x-ssl-client-verify $ssl_client_verify;
1889+
proxy_set_header x-ssl-client-s-dn $ssl_client_s_dn;
1890+
```
1891+
```apache
1892+
# Apache
1893+
RequestHeader set x-ssl-client-verify ""
1894+
RequestHeader set x-ssl-client-verify "%{SSL_CLIENT_VERIFY}s"
1895+
RequestHeader set x-ssl-client-s-dn ""
1896+
RequestHeader set x-ssl-client-s-dn "%{SSL_CLIENT_S_DN}s"
1897+
```
1898+
</details>
18641899

18651900
### ttl
18661901

docs/update-configuration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const props = [
6565
read.on('line', (line) => {
6666
let strLine = line.trim();
6767

68-
if (strLine.endsWith('```js')) {
68+
if (strLine.endsWith('```js') || strLine.endsWith('```apache') || strLine.endsWith('```nginx')) {
6969
inTicks = true;
7070
}
7171

lib/consts/client_attributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const STRING = [
9898
'request_object_signing_alg',
9999
'sector_identifier_uri',
100100
'subject_type',
101+
'tls_client_auth_subject_dn',
101102
'token_endpoint_auth_method',
102103
'tos_uri',
103104
'userinfo_encrypted_response_alg',

lib/helpers/client_schema.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ module.exports = function getSchema(provider) {
4141
DEFAULT.subject_type = 'pairwise';
4242
}
4343

44+
const tlsClientAuthEnabled = ['token', 'revocation', 'introspection']
45+
.find(endpoint => configuration[`${endpoint}EndpointAuthMethods`].includes('tls_client_auth'));
46+
47+
if (tlsClientAuthEnabled) {
48+
RECOGNIZED_METADATA.push('tls_client_auth_subject_dn');
49+
}
50+
4451
if (features.introspection) {
4552
RECOGNIZED_METADATA.push('introspection_endpoint_auth_method');
4653
RECOGNIZED_METADATA.push('introspection_endpoint_auth_signing_alg');
@@ -228,17 +235,26 @@ module.exports = function getSchema(provider) {
228235
return lengths;
229236
}, new Set());
230237

231-
for (const endpoint of ['token', 'introspection', 'revocation']) { // eslint-disable-line no-restricted-syntax
232-
if (
233-
this[`${endpoint}_endpoint_auth_method`] === 'client_secret_jwt'
234-
&& this[`${endpoint}_endpoint_auth_signing_alg`] === undefined
235-
) {
236-
const required = Math.max(...configuration[`${endpoint}EndpointAuthSigningAlgValues`]
237-
.filter(alg => alg.startsWith('HS')).map(alg => parseInt(alg.slice(-3), 10)));
238+
['token', 'introspection', 'revocation'].forEach((endpoint) => {
239+
switch (this[`${endpoint}_endpoint_auth_method`]) {
240+
case 'client_secret_jwt':
241+
if (this[`${endpoint}_endpoint_auth_signing_alg`] === undefined) {
242+
const required = Math.max(...configuration[`${endpoint}EndpointAuthSigningAlgValues`]
243+
.filter(alg => alg.startsWith('HS')).map(alg => parseInt(alg.slice(-3), 10)));
244+
245+
hsLengths.add(required);
246+
}
247+
break;
238248

239-
hsLengths.add(required);
249+
case 'tls_client_auth':
250+
if (!this.tls_client_auth_subject_dn) {
251+
invalidate('tls_client_auth_subject_dn must be provided for tls_client_auth');
252+
}
253+
break;
254+
255+
default:
240256
}
241-
}
257+
});
242258

243259
const required = _.max(Array.from(hsLengths));
244260

@@ -286,6 +302,9 @@ module.exports = function getSchema(provider) {
286302
const requireJwks = this.token_endpoint_auth_method === 'private_key_jwt'
287303
|| this.introspection_endpoint_auth_method === 'private_key_jwt'
288304
|| this.revocation_endpoint_auth_method === 'private_key_jwt'
305+
// || this.token_endpoint_auth_method === 'self_signed_tls_client_auth'
306+
// || this.introspection_endpoint_auth_method === 'self_signed_tls_client_auth'
307+
// || this.revocation_endpoint_auth_method === 'self_signed_tls_client_auth'
289308
|| (requestSignAlgRequiringJwks.exec(this.request_object_signing_alg))
290309
|| (encAlgRequiringJwks.exec(this.id_token_encrypted_response_alg))
291310
|| (encAlgRequiringJwks.exec(this.userinfo_encrypted_response_alg))

lib/helpers/configuration.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,24 @@ class Configuration {
7676
}
7777
}
7878

79-
/* eslint-disable no-restricted-syntax */
79+
/* istanbul ignore if */
8080
if (process.env.NODE_ENV !== 'test') {
81-
for (const flag in this.features) {
82-
if (this.features[flag] && !STABLE_FLAGS.includes(flag)) {
81+
Object.entries(this.features).forEach(([flag, value]) => {
82+
if (value && !STABLE_FLAGS.includes(flag)) {
8383
attention.info(`a draft/experimental feature (${flag}) enabled, future updates to \
8484
this feature will be released as MINOR releases`);
8585
}
86+
});
87+
88+
/* eslint-disable no-restricted-syntax */
89+
for (const endpoint of ['token', 'introspection', 'revocation']) {
90+
if (this[`${endpoint}EndpointAuthMethods`].includes('tls_client_auth')) {
91+
attention.info('a draft/experimental feature (tls_client_auth) enabled, future updates to this feature will be released as MINOR releases');
92+
break;
93+
}
8694
}
95+
/* eslint-enable */
8796
}
88-
/* eslint-enable */
8997
}
9098
}
9199

lib/helpers/defaults.js

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const DEFAULTS = {
3939
/*
4040
* claims
4141
*
42-
* description: List of the Claim Names of the Claims that the OpenID Provider MAY be able to
42+
* description: Array of the Claim Names of the Claims that the OpenID Provider MAY be able to
4343
* supply values for.
4444
* affects: discovery, ID Token claim names, Userinfo claim names
4545
*/
@@ -555,7 +555,7 @@ const DEFAULTS = {
555555
/*
556556
* prompts
557557
*
558-
* description: List of the prompt values that the OpenID Provider MAY be able to resolve
558+
* description: Array of the prompt values that the OpenID Provider MAY be able to resolve
559559
* affects: authorization
560560
*/
561561
prompts: ['consent', 'login', 'none'],
@@ -564,7 +564,7 @@ const DEFAULTS = {
564564
/*
565565
* responseTypes
566566
*
567-
* description: List of response_type values that OP supports
567+
* description: Array of response_type values that OP supports
568568
* affects: authorization, discovery, registration, registration management
569569
*/
570570
responseTypes: [
@@ -602,7 +602,7 @@ const DEFAULTS = {
602602
/*
603603
* scopes
604604
*
605-
* description: List of the scope values that the OP supports
605+
* description: Array of the scope values that the OP supports
606606
* affects: discovery, authorization, ID Token claims, Userinfo claims
607607
*/
608608
scopes: ['openid', 'offline_access'],
@@ -611,7 +611,7 @@ const DEFAULTS = {
611611
/*
612612
* dynamicScopes
613613
*
614-
* description: List of the dynamic scope values that the OP supports. These must be regular
614+
* description: Array of the dynamic scope values that the OP supports. These must be regular
615615
* expressions that the OP will check string scope values, that aren't in the static list,
616616
* against.
617617
* affects: discovery, authorization, ID Token claims, Userinfo claims
@@ -632,7 +632,7 @@ const DEFAULTS = {
632632
/*
633633
* subjectTypes
634634
*
635-
* description: List of the Subject Identifier types that this OP supports. Valid types are
635+
* description: Array of the Subject Identifier types that this OP supports. Valid types are
636636
* - `public`
637637
* - `pairwise`
638638
* affects: discovery, registration, registration management, ID Token and Userinfo sub claim
@@ -664,9 +664,56 @@ const DEFAULTS = {
664664
/*
665665
* tokenEndpointAuthMethods
666666
*
667-
* description: List of Client Authentication methods supported by this OP's Token Endpoint
667+
* description: Array of Client Authentication methods supported by this OP's Token Endpoint
668668
* affects: discovery, client authentication for token endpoint, registration and
669669
* registration management
670+
* example: Supported values list
671+
* ```js
672+
* [
673+
* 'none',
674+
* 'client_secret_basic', 'client_secret_post',
675+
* 'client_secret_jwt', 'private_key_jwt',
676+
* 'tls_client_auth',
677+
* ]
678+
* ```
679+
* example: Setting up tls_client_auth
680+
* To enable `tls_client_auth` the provider expects `x-ssl-client-verify` and `x-ssl-client-s-dn`
681+
* headers to be presented by your TLS-offloading proxy with the variable values set by these
682+
* proxies. An important aspect is to sanitize the inbound request headers at the proxy.
683+
*
684+
* <br/><br/>
685+
*
686+
* The most common openssl based proxies are Apache and NGINX, with those you're looking to use
687+
*
688+
* <br/><br/>
689+
*
690+
* __`SSLVerifyClient` (Apache) / `ssl_verify_client` (NGINX)__
691+
* `require` - if you only support tls_client_auth,
692+
* `optional` if you also support additional non-MTLS based authentication methods,
693+
* `optional_no_ca` - if you also support additional non-MTLS based authentication methods AND self_signed_tls_client_auth (not implemented yet)
694+
*
695+
* <br/><br/>
696+
*
697+
* __`SSLCACertificateFile` or `SSLCACertificatePath` (Apache) / `ssl_client_certificate` (NGINX)__ with the values pointing to your accepted CA Certificates
698+
*
699+
* <br/><br/>
700+
*
701+
* Set the proxy request headers with variables set as a result of enabling MTLS
702+
*
703+
* ```nginx
704+
* # NGINX
705+
* proxy_set_header x-ssl-client-verify $ssl_client_verify;
706+
* proxy_set_header x-ssl-client-s-dn $ssl_client_s_dn;
707+
* ```
708+
*
709+
* ```apache
710+
* # Apache
711+
* RequestHeader set x-ssl-client-verify ""
712+
* RequestHeader set x-ssl-client-verify "%{SSL_CLIENT_VERIFY}s"
713+
* RequestHeader set x-ssl-client-s-dn ""
714+
* RequestHeader set x-ssl-client-s-dn "%{SSL_CLIENT_S_DN}s"
715+
* ```
716+
*
670717
*/
671718
tokenEndpointAuthMethods: [
672719
'none',
@@ -1559,7 +1606,9 @@ const DEFAULTS = {
15591606
/*
15601607
* introspectionEndpointAuthMethods
15611608
*
1562-
* description: List of Client Authentication methods supported by this OP's Introspection Endpoint
1609+
* description: Array of Client Authentication methods supported by this OP's Introspection Endpoint.
1610+
* If no configuration value is provided the same values as for tokenEndpointAuthMethods will be
1611+
* used. Supported values list is the same as for tokenEndpointAuthMethods.
15631612
* affects: discovery, client authentication for introspection, registration and registration
15641613
* management
15651614
*/
@@ -1568,7 +1617,9 @@ DEFAULTS.introspectionEndpointAuthMethods = DEFAULTS.tokenEndpointAuthMethods;
15681617
/*
15691618
* revocationEndpointAuthMethods
15701619
*
1571-
* description: List of Client Authentication methods supported by this OP's Revocation Endpoint
1620+
* description: Array of Client Authentication methods supported by this OP's Revocation Endpoint.
1621+
* If no configuration value is provided the same values as for tokenEndpointAuthMethods will be
1622+
* used. Supported values list is the same as for tokenEndpointAuthMethods.
15721623
* affects: discovery, client authentication for revocation, registration and registration
15731624
* management
15741625
*/

lib/models/client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const { LOOPBACKS } = require('../consts/client_attributes');
1919
const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y'];
2020
const KEY_TYPES = ['RSA', 'EC'];
2121

22-
const nonSecretAuthMethods = ['private_key_jwt', 'none'];
22+
const nonSecretAuthMethods = ['private_key_jwt', 'none', 'tls_client_auth'];
2323
const clientEncryptions = [
2424
'id_token_encrypted_response_alg',
2525
'request_object_encryption_alg',

0 commit comments

Comments
 (0)