Home > Back-end >  SSL Certificate is unable to be verified when sending request to the same machine
SSL Certificate is unable to be verified when sending request to the same machine

Time:05-01

I have a remote Ubuntu machine running a node server with next.js and using next-auth for authentication. Everything works fine with HTTP locally.

Configuration

Here is the code that runs the node server on HTTPS and uses next.js.

const https = require('https');
const fs = require('fs');
const next = require('next')
const port = 3000;
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev, dir: __dirname })
const handle = app.getRequestHandler()
const { parse } = require('url');

const options = {
    key: fs.readFileSync('./path/to/private-key'),
    cert: fs.readFileSync('./path/to/csr'),
    ca: [fs.readFileSync('./path/to/gdroot-g2')]
};

app.prepare().then(() => {
    https.createServer(options, (req, res) => {
      const parsedUrl = parse(req.url, true);
      handle(req, res, parsedUrl);
    }).listen(port, err => {
        if (err) throw err
        console.log(`> Ready on localhost:${port}`)
    })
})

The server runs on port 3000, so I used sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3000 to redirect requests to 443.

I bought a Deluxe SSL certificate registered for example.com and also bought the domain name, which has an A record pointing it to the server's ip.

Here is the server's /etc/hosts file (excluding the IPv6 hosts).

127.0.0.1       localhost
192.XXX.XXX.XX server.example.com server

In my .env, I set NEXTAUTH_URL=https://example.com:3000 in order for it to send its requests there.

The Problem

On the production build, when next-auth attempts to get the user's session, it sends an HTTPS request from the server to itself using the domain name (Ex: example.com). The server then errors with request to https://mDomain.com:3000/api/auth/session failed, reason: unable to verify the first certificate.

If I change NEXTAUTH_URL=https://example.com (without the port), node instead errors with request to https://example.com/api/auth/session failed, reason: connect ECONNREFUSED 192.XXX.XXX.XX:443.

Similarly, if I use curl (curl https://example.com:3000/api/auth/session), curl responds with curl: (60) SSL certificate problem: unable to get local issuer certificate and the request is never even received by node (so node never errors).

Note that if I use curl from my own machine, it sends the request and node receives it but promptly throws the request failed, unable to verify... error (I believe this is because the certificate works from my machine to the server, but the subsequent request from the server to the server causes the error).

I have tried setting NEXTAUTH_URL_INTERNAL but the error is the same (I have tried setting it to the domain name, ip address, and localhost). However, I do not think this is the actual root of the issue.

It appears that the root of the problem is that when the server receives an HTTPS request from itself, the domain name doesn't match example.com (perhaps it comes from 127.0.0.1:3000 or its ip address) and therefore it is unable to verify it. All HTTPS requests work when the sender is another machine.

The only solution I can think of is creating a self-signed certificate for localhost on the server (then I could point next-auth to use localhost internally). That would then require the server to use 2 SSL certificates, which might not be possible either. According to next-auth, however, everything should work fine with HTTPS so hopefully, I have just made a fixable misconfiguration.

Thank you for any help and please let me know if you have any ideas for solutions.

Update

I experimented more with NEXTAUTH_URL_INTERNAL and discovered that if I start an HTTPS server on port 3000 and an HTTP server on port 3001 and set NEXTAUTH_URL_INTERNAL=http://127.0.0.1:3001 it does work.

However, wouldn't it be a security vulnerability to have an HTTP server on port 3001 (users can still connect to this port) and to allow next-auth to internally use HTTP? Would it be possible to have NEXTAUTH_URL_INTERNAL=https://127.0.0.1:3000 and self-sign a certificate for localhost? Or would that also be a vulnerability?

Note that currently, without a self-signed certificate for localhost, if I set NEXTAUTH_URL_INTERNAL=https://127.0.0.1:3000 then node errors with request to https://127.0.0.1:3000/api/auth/session failed, reason: Hostname/IP does not match certificate's altnames: IP: 127.0.0.1 is not in the cert's list:. Maybe I could add localhost to the altnames? Or maybe even the server's IP?

CodePudding user response:

I ended up fixing it by running another HTTPS server (for the same next app) on a separate port with a self-signed certificate for localhost. I then set NEXTAUTH_URL_INTERNAL to query localhost on that port. I also had to use mkcert to create my own CA. By default, node's fetch doesn't recognize my custom CA so I added NODE_EXTRA_CA_CERTS="/path/to/rootCA.pem to /etc/environment.

All of this allowed next-auth to query localhost over HTTPS and validate the certificate (even though it's self-signed).

  • Related