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).