Repository navigation
Reloading page bypasses TLS client authentication #35317
Copy link
Copy link
Labels
docIssues and PRs related to Node.js documentation.Issues and PRs related to Node.js documentation.tlsIssues and PRs related to the tls subsystem.Issues and PRs related to the tls subsystem.
Description
- Version: v12.18.3
- Platform: Linux 5.8.8-100.fc31.x86_64 deps: update openssl to 1.0.1j #1 SMP Wed Sep 9 20:29:23 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
- Subsystem:
What steps will reproduce the bug?
- Obtain (or create) a CA certificate, a server certificate, and a server key supplied by said CA.
- Compile (with typescript) and run the following code:
import fs from 'fs';
import path from 'path';
import https from 'https';
import tls from 'tls';
import express from 'express';
/* Alias environment variables */
const port = process.env.PORT || 443;
const httpsOptions = {
key: fs.readFileSync(path.join('certs', 'server.key')),
cert: fs.readFileSync(path.join('certs', 'server.crt')),
ca: fs.readFileSync(path.join('certs', 'ca.crt')),
requestCert: true,
rejectUnauthorized: false /* This is necessary to accept self-signed certificates, we will perform the authentication ourselves */
};
/* Create Express app */
const expressApp = express();
const expressServer = https.createServer(httpsOptions, expressApp);
expressApp.use((req,res,next) => {
console.log((req.socket as tls.TLSSocket).authorized ? 'true' : 'false');
if (!(req.socket as tls.TLSSocket).authorized) {
return res.status(401).send('Unauthorized');
}
next();
});
expressServer.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
- Open Firefox (version 80) and go to https://localhost. You should have your connection rejected (output is 'false').
- Reload the page. Your connection is now accepted (output is 'true').
- This curiously doesn't happen on Chromium.
How often does it reproduce? Is there a required condition?
- Always reproducible. Chromium seems to not trigger the issue.
What is the expected behavior?
- Connection should always be rejected
What do you see instead?
- Connection is accepted after page reload
Additional information
- According to documentation at https://nodejs.org/api/tls.html#tls_tlssocket_authorized the
(req.socket as tls.TLSSocket).authorizedflag should always be false if the client certificate doesn't match the server CA. In this case it becomes true after a page reload from Firefox for unknown reasons. - This issue is described also in a stack overflow entry: https://stackoverflow.com/questions/60367338/result-of-req-socket-authorized-is-not-correct-using-nodejs-https-and-express
- I'm guessing it might be due to some TLS connection reuse logic.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
docIssues and PRs related to Node.js documentation.Issues and PRs related to Node.js documentation.tlsIssues and PRs related to the tls subsystem.Issues and PRs related to the tls subsystem.
Activity
bnoordhuis commentedon Sep 24, 2020
Close, it's not the connection but the TLS session that's reused. :-)
Reuse cuts the handshake short (and cuts out the client certificate exchange) because it reuses the previously established session parameters. That's per spec and normally what you want. Chromium probably creates a new session when you reload.
You can force a new session by checking
socket.isSessionReused()and then callingsocket.renegotiate()but caveat emptor, only up to TLS v1.2 - TLS v1.3 doesn't support renegotiation.That's an issue on our side (and I can see why it's surprising behavior) but I hesitate to call it an outright bug.
socket.authorizedisfalsewhen a verification error happened during the handshake (e.g. invalid or untrusted certificate) buttrueotherwise.A new connection started from a resumed session doesn't do that verification and hence assumes
socket.authorized = true. The nature of TLS sessions is such that I'm not sure this can be fixed even if we wanted to.I do feel the documentation for
socket.authorizedshould mention that caveat. Currently it only says this:I've added the
doclabel. Pull request welcome!maikeriva commentedon Sep 29, 2020
Thanks for the answer!
However, I believe that if this is not a code bug it is at least a severe security flaw and should be addressed as such. I found many many examples on the web for client authentication which relied on the concept that
socket.authorized == false, and god knows how many websites are relying on that. That might be due to a documentation gap, yet the effect on security is the same.Furthermore, thanks for explaining how to verify the certificate through each request. However I think that approach has two flaws:
1: Renegotiation is forced on each request, and I believe that's expensive for the server.
2: Such approach won't work on TLS 1.3 (which alternatives are there?)
I believe that ensuring that keeping
socket.authorized == falseby default could solve all of these problems in a simple manner.bnoordhuis commentedon Sep 29, 2020
I won't argue you're wrong but keep in mind this only happens because your script turns off authentication with
{ rejectUnauthorized: false }. The default ensures sessions are properly authenticated before they're established.The comment in your script says it's to allow self-signed certificates. Self-signed certificates by themselves don't provide authentication, they're just a pinky promise that the signer is who he says he is.
I assume you're checking the certificate fingerprint as a way to authenticate the client? That's okay-ish from a security viewpoint but consider moving to a self-signed CA certificate that signs your client certificates. That way you're working with rather than around the system. :-)
TLSv1.3 has a concept of post-handshake authentication but Node.js doesn't support that yet and might never because it interacts badly with HTTP/2, see RFC 8740.
You're welcome to open a pull request but be ready for some pushback because that's a backwards incompatible change with a lot of potential for ecosystem fallout.
maikeriva commentedon Sep 29, 2020
That's true, I didn't notice that turning
rejectUnauthorized: trueactually enforces the check at each page reload. I keep it false because we want to use a fallback authentication mechanism alongside client certificates.The comment in the code snippet is outdated, actually I am using a self-signed CA which in turn issued server and client certificates.
I guess you might be right about retrocompatibility of such a pull request (even though developers which stuck to the documentation are going to have the expected behaviour, yet I also expect that the ones who figured out the quirk are possibly going to be surprised).
I think a documentation update is likely the best approach, yet I would also issue alongside it an official notice to inform developers who didn't figure this out.
bnoordhuis commentedon Sep 29, 2020
Do you want to open a documentation pull request?
I'm not quite sure what you mean by "official notice" - a note in the release notes?
maikeriva commentedon Oct 2, 2020
Yep, exactly! Sorry I could not find the correct words.
I might consider doing a pull request later on on, everyone is free to make its own though without waiting mine :)
luigidt commentedon Oct 30, 2020
Hey guys, I was going a little crazy because of the same issue...
Is it possible to implement the same behavior in the handler?
(I tried to look for the default implementation but just couldn't find it)
eraytufan commentedon Jan 20, 2021
I have a similar issue. However in my case
socket.authorizedturns into atruewhen I send the same request second time even when client certificate is never authenticated. Surely, I am not the only one and it must be a bug. Otherwise, I don't see the benefit of adding such method and like others have said it is very misleading.Edit: I found the issue, I don't know if I still call it a bug. I think the issue depends on both client and server. In my part, if client and server handshake is done at least even once, I could turn off the client, close the connection or even change the client certificate, the client was able to reconnect the server without any problems. I think if the client saves the
previously established sessionin some way then it can always reconnect to the server.I think documentation should be updated like others have suggested. Unfortunately, I saw the same issue in a lot of code snippets and it might lead to security issues if the developers do not realize it.
donnd-t commentedon May 12, 2021
Would be nice to have a solution for this or at least have it documented. This was unexpected behavior for me.
Tarrowren commentedon Jul 13, 2022
chrome also triggers this issue. You need to wait for about a minute before the second request
github-actions commentedon Jun 27, 2026
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
soreavis commentedon Jul 18, 2026
Re-measured this on current main before writing the doc text: when the client presented a certificate, the verification result — including the error — carries across resumption on both TLS 1.2 and 1.3 (
SSL_get_verify_resultreads the session-stored result), soauthorizeddoesn't flip totruein that case. The path that still flips is TLS 1.3 with no client certificate at all: full handshakefalse→ resumedtrue, withgetPeerCertificate()empty — the PSK carve-out from #23188. Opened #64584 documenting the caveat as invited, with the practicalisSessionReused()+ peer-cert check forrejectUnauthorized: falseservers.pimterry commentedon Jul 22, 2026
I know it's been a while, but I do think this is cleanly fixable to work as expected, and we should reconsider the current behaviour. In addition to the docs PR that @soreavis opened documenting the current state, I've opened a PR to change the behaviour itself in future here: #64677
doc: clarify tlsSocket.authorized on resumption
doc: clarify tlsSocket.authorized on resumption
doc: clarify tlsSocket.authorized on resumption
doc: clarify tlsSocket.authorized on resumption
doc: clarify tlsSocket.authorized on resumption