client-certificate-auth 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -4
- package/lib/clientCertificateAuth.d.ts +1 -1
- package/lib/clientCertificateAuth.js +1 -1
- package/lib/parsers.js +45 -1
- package/package.json +21 -14
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# client-certificate-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Comprehensive toolkit for client SSL certificate authentication (mTLS) in Node.js. Includes Express/Connect middleware, framework-agnostic certificate extraction for reverse proxies (AWS ALB, Envoy, Cloudflare, Traefik, and more), and pre-built authorization helpers.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/tgies/client-certificate-auth/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/client-certificate-auth)
|
|
7
7
|
[](https://codecov.io/gh/tgies/client-certificate-auth)
|
|
8
8
|
[](https://dashboard.stryker-mutator.io/reports/github.com/tgies/client-certificate-auth/master)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
[**Full Documentation**](https://tgies.github.io/client-certificate-auth/) - guides, API reference, and runnable examples
|
|
11
|
+
[**Commercial Support**](#commercial-support) - consulting, custom features, and priority support for production deployments
|
|
12
|
+
|
|
13
|
+
**Recommended by AWS** - Featured in the [AWS API Gateway documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-client-side-ssl-authentication.html#certificate-validation).
|
|
14
|
+
|
|
15
|
+
**Fanatically Tested** - 100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~4,776 lines of test code for ~711 lines of source (measured by [cloc](https://github.com/AlDanial/cloc)).
|
|
11
16
|
|
|
12
17
|
## Installation
|
|
13
18
|
|
|
@@ -19,9 +24,11 @@ npm install client-certificate-auth
|
|
|
19
24
|
|
|
20
25
|
## Synopsis
|
|
21
26
|
|
|
22
|
-
This
|
|
27
|
+
This library provides everything you need to implement mutual TLS (mTLS) authentication in Node.js. It extracts client certificates from direct TLS connections (`req.socket`) or from HTTP headers forwarded by reverse proxies (AWS ALB, Envoy, Cloudflare, Traefik, nginx, HAProxy).
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
The certificate is parsed into a standard `tls.PeerCertificate` object and passed to your callback for authorization logic.
|
|
30
|
+
|
|
31
|
+
Compatible with Express, Connect, or any Node.js HTTP server framework by using the framework-agnostic `extractClientCertificate` function.
|
|
25
32
|
|
|
26
33
|
## Usage
|
|
27
34
|
|
|
@@ -292,6 +299,8 @@ This package provides everything you need to build mTLS authentication for any N
|
|
|
292
299
|
|
|
293
300
|
If you build an adapter for another framework (Koa, Fastify, Hapi, NestJS, etc.), please open an issue or PR to get it listed here!
|
|
294
301
|
|
|
302
|
+
> For complete API documentation with all types, parameters, and examples, see the [API Reference](https://tgies.github.io/client-certificate-auth/api/).
|
|
303
|
+
|
|
295
304
|
### Accessing the Certificate
|
|
296
305
|
|
|
297
306
|
After authentication, the certificate is attached to `req.clientCertificate` for downstream handlers:
|
|
@@ -903,6 +912,30 @@ The sync CJS wrapper does not support reverse proxy options (`certificateSource`
|
|
|
903
912
|
|
|
904
913
|
The `/helpers`, `/parsers`, and `/extractor` subpath exports each provide a `load()` function in CJS. See [Subpath Exports in CJS](#subpath-exports-in-cjs) for details.
|
|
905
914
|
|
|
915
|
+
## Commercial Support
|
|
916
|
+
|
|
917
|
+
`client-certificate-auth` is built and maintained by [Tony Gies](https://github.com/tgies). For organizations running it in production, commercial support is available through his consultancy, Crash United, LLC.
|
|
918
|
+
|
|
919
|
+
### Support Offerings
|
|
920
|
+
|
|
921
|
+
| Service | Description |
|
|
922
|
+
|---------|-------------|
|
|
923
|
+
| **Priority bug fixes** | Reported issues triaged and patched ahead of the public queue |
|
|
924
|
+
| **Custom features & integrations** | Adapters for new reverse proxies, encoding formats, or framework wrappers |
|
|
925
|
+
| **mTLS architecture consulting** | Review of your certificate issuance, rotation, and trust-chain design |
|
|
926
|
+
| **Deployment security review** | Threat modeling for your specific proxy + middleware + auth flow |
|
|
927
|
+
| **Private security advisories** | Coordinated disclosure for vulnerabilities affecting your deployment |
|
|
928
|
+
|
|
929
|
+
For pricing, scoping, or anything not listed above, email **[support@crashunited.com](mailto:support@crashunited.com)** to discuss your needs.
|
|
930
|
+
|
|
931
|
+
### Sponsorship
|
|
932
|
+
|
|
933
|
+
To support ongoing development without a formal contract, [GitHub Sponsors](https://github.com/sponsors/tgies) is the simplest path.
|
|
934
|
+
|
|
935
|
+
### Enterprise Procurement
|
|
936
|
+
|
|
937
|
+
This package is enrolled in [Tidelift](https://tidelift.com/) (now part of SonarQube Advanced Security). If your organization already subscribes, `client-certificate-auth` is included in your coverage for security disclosures, license compliance, and version metadata.
|
|
938
|
+
|
|
906
939
|
## License
|
|
907
940
|
|
|
908
941
|
MIT © Tony Gies
|
|
@@ -135,7 +135,7 @@ export type Middleware = (
|
|
|
135
135
|
* Express/Connect middleware for client SSL certificate authentication.
|
|
136
136
|
*
|
|
137
137
|
* @param callback - Validation function that receives the client certificate
|
|
138
|
-
* and returns true/false (sync) or Promise<boolean
|
|
138
|
+
* and returns true/false (sync) or `Promise<boolean>` (async).
|
|
139
139
|
* @param options - Configuration options
|
|
140
140
|
* @returns Express middleware function
|
|
141
141
|
*
|
|
@@ -46,7 +46,7 @@ import { extractClientCertificate } from './extractor.js';
|
|
|
46
46
|
*
|
|
47
47
|
* @param {(cert: import('tls').PeerCertificate, req: ClientCertRequest) => boolean | Promise<boolean>} callback
|
|
48
48
|
* Validation function that receives the client certificate and the request
|
|
49
|
-
* object. Returns true/false (sync) or a Promise<boolean
|
|
49
|
+
* object. Returns true/false (sync) or a `Promise<boolean>` (async) to
|
|
50
50
|
* allow/deny access.
|
|
51
51
|
* @param {ClientCertificateAuthOptions} [options={}]
|
|
52
52
|
* @returns {Middleware}
|
package/lib/parsers.js
CHANGED
|
@@ -93,7 +93,51 @@ export function parseUrlPemAws(headerValue) {
|
|
|
93
93
|
// Must escape before decodeURIComponent or + becomes space
|
|
94
94
|
const escaped = headerValue.replace(/\+/g, '%2B');
|
|
95
95
|
const pem = decodeURIComponent(escaped);
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
// AWS sends the full chain as concatenated PEM blocks. Split into
|
|
98
|
+
// individual blocks and parse each, then link via issuerCertificate
|
|
99
|
+
// (mirrors parseBase64Der's chain handling for Traefik/Cloudflare).
|
|
100
|
+
// Node's X509Certificate throws on multi-block input, so without this
|
|
101
|
+
// split the entire request fails when AWS forwards a real chain.
|
|
102
|
+
//
|
|
103
|
+
// Uses indexOf scanning rather than regex to avoid polynomial-time
|
|
104
|
+
// backtracking on adversarial input (e.g., many unterminated BEGIN
|
|
105
|
+
// markers). Total work is O(N) in the input length.
|
|
106
|
+
const pemBlocks = [];
|
|
107
|
+
const beginMarker = '-----BEGIN CERTIFICATE-----';
|
|
108
|
+
const endMarker = '-----END CERTIFICATE-----';
|
|
109
|
+
let scanPos = 0;
|
|
110
|
+
while (true) {
|
|
111
|
+
const begin = pem.indexOf(beginMarker, scanPos);
|
|
112
|
+
if (begin === -1) {break;}
|
|
113
|
+
const end = pem.indexOf(endMarker, begin + beginMarker.length);
|
|
114
|
+
if (end === -1) {break;}
|
|
115
|
+
pemBlocks.push(pem.substring(begin, end + endMarker.length));
|
|
116
|
+
scanPos = end + endMarker.length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (pemBlocks.length === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const certs = pemBlocks.map(block => {
|
|
124
|
+
try {
|
|
125
|
+
return pemToCertificate(block);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}).filter(Boolean);
|
|
130
|
+
|
|
131
|
+
if (certs.length === 0) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Link the cert chain via issuerCertificate
|
|
136
|
+
for (let i = 0; i < certs.length - 1; i++) {
|
|
137
|
+
certs[i].issuerCertificate = certs[i + 1];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return certs[0];
|
|
97
141
|
} catch {
|
|
98
142
|
return null;
|
|
99
143
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-certificate-auth",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Express/Connect middleware for mTLS client certificate authentication with reverse proxy support (AWS ALB, Envoy, Cloudflare, Traefik)",
|
|
5
5
|
"homepage": "https://github.com/tgies/client-certificate-auth",
|
|
6
6
|
"bugs": {
|
|
@@ -72,21 +72,24 @@
|
|
|
72
72
|
"node": ">= 20"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@commitlint/cli": "^20.4.
|
|
76
|
-
"@commitlint/config-conventional": "^20.
|
|
77
|
-
"@stryker-mutator/core": "^9.
|
|
78
|
-
"@stryker-mutator/jest-runner": "^9.
|
|
75
|
+
"@commitlint/cli": "^20.4.3",
|
|
76
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
77
|
+
"@stryker-mutator/core": "^9.6.0",
|
|
78
|
+
"@stryker-mutator/jest-runner": "^9.6.1",
|
|
79
79
|
"@types/express": "^5.0.6",
|
|
80
|
-
"@types/node": "^25.
|
|
81
|
-
"c8": "^
|
|
82
|
-
"eslint": "^9.
|
|
83
|
-
"globals": "^17.
|
|
80
|
+
"@types/node": "^25.6.0",
|
|
81
|
+
"c8": "^11.0.0",
|
|
82
|
+
"eslint": "^9.39.4",
|
|
83
|
+
"globals": "^17.5.0",
|
|
84
84
|
"husky": "^9.1.7",
|
|
85
|
-
"jest": "^30.
|
|
86
|
-
"lint-staged": "^16.2
|
|
85
|
+
"jest": "^30.3.0",
|
|
86
|
+
"lint-staged": "^16.3.2",
|
|
87
87
|
"selfsigned": "^5.5.0",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
88
|
+
"typedoc": "^0.28.19",
|
|
89
|
+
"typedoc-plugin-markdown": "^4.11.0",
|
|
90
|
+
"typescript": "^6.0.3",
|
|
91
|
+
"vitepress": "^2.0.0-alpha.17",
|
|
92
|
+
"ws": "^8.20.0"
|
|
90
93
|
},
|
|
91
94
|
"directories": {
|
|
92
95
|
"lib": "./lib",
|
|
@@ -103,7 +106,11 @@
|
|
|
103
106
|
"build:types": "tsc --declaration --emitDeclarationOnly --outDir lib",
|
|
104
107
|
"check": "npm run lint && npm run typecheck && npm run test:coverage",
|
|
105
108
|
"stats": "echo '=== Source ===' && cloc lib/ --quiet | tail -n +2 && echo '=== Tests ===' && cloc test/ --exclude-dir=docker --quiet | tail -n +2 && echo '=== Package ===' && npm pack --dry-run 2>&1 | grep -E 'package size|unpacked size|total files'",
|
|
106
|
-
"prepare": "husky"
|
|
109
|
+
"prepare": "husky",
|
|
110
|
+
"docs:api": "typedoc",
|
|
111
|
+
"docs:dev": "vitepress dev docs",
|
|
112
|
+
"docs:build": "npm run docs:api && vitepress build docs",
|
|
113
|
+
"docs:preview": "vitepress preview docs"
|
|
107
114
|
},
|
|
108
115
|
"repository": {
|
|
109
116
|
"type": "git",
|