Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when fetching session in API #212

Closed
TwoAbove opened this issue Jun 5, 2020 · 24 comments
Closed

Error when fetching session in API #212

TwoAbove opened this issue Jun 5, 2020 · 24 comments
Labels
question Ask how to do something or how something works

Comments

@TwoAbove
Copy link
Contributor

TwoAbove commented Jun 5, 2020

Hello again!

I'm having a problem when fetching a session in an api route. I get this error:

app_1          | CLIENT_FETCH_ERROR https://localhost:3000/api/auth/session FetchError: request to https://localhost:3000/api/auth/session failed, reason: write EPROTO 140431569815360:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:332:
app_1          | 
app_1          |     at ClientRequest.<anonymous> (/app/node_modules/next/dist/compiled/node-fetch/index.js:1:147710)
app_1          |     at ClientRequest.emit (events.js:310:20)
app_1          |     at TLSSocket.socketErrorListener (_http_client.js:426:9)
app_1          |     at TLSSocket.emit (events.js:310:20)
app_1          |     at errorOrDestroy (internal/streams/destroy.js:108:12)
app_1          |     at onwriteError (_stream_writable.js:424:5)
app_1          |     at onwrite (_stream_writable.js:445:5)
app_1          |     at internal/streams/destroy.js:50:7
app_1          |     at TLSSocket.Socket._destroy (net.js:677:5)
app_1          |     at TLSSocket.destroy (internal/streams/destroy.js:38:8)
app_1          |     at WriteWrap.onWriteComplete [as oncomplete] (internal/stream_base_commons.js:93:12) {
app_1          |   type: 'system',
app_1          |   errno: 'EPROTO',
app_1          |   code: 'EPROTO'
app_1          | }
app_1          | session null

The code is simple:

import { session as getSession } from 'next-auth/client'

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const session = await getSession({ req });
  console.log('session', session);
  ...

Any pointers for how I can debug this? I'm not sure where to start.

Thanks!

@iaincollins iaincollins added bug Something isn't working question Ask how to do something or how something works labels Jun 5, 2020
@iaincollins
Copy link
Member

Hmm I think you might want to use http://localhost instead of https://localhost unless you have HTTPS set up (but it does work if you do have HTTPS setup).

If you DO have HTTPS set up it might be that your browser isn't able to validate your locally signed certificate.

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

I think I found the issue.

I think there needs to be a separate function to fetch the session for the server-side.

The error is because it looks like the server requests the session object through a http request, instead of using the DB.

I have this behind an nginx ingress and inside a docker container.

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

So the cert authority that I'm using is on my machine (so ssl works in browser), while node can't verify the certs?

I'll dig a bit deeper and check the certs

@iaincollins
Copy link
Member

iaincollins commented Jun 5, 2020

Yeah, that sound about right, I think it's probably a consequence of the configuration here.

The NextAuth.js client uses the build in fetch() in Next.js to make the call, I'm not sure how to tell that globally to use certificate / disable SSL. (If it turns out there isn't a way, we can always try and figure out how to set that at runtime in NextAuth.js for folks that need it).

@geraldnolan might be able to help with this, he wrote the documentation for apple which needs HTTPS to run locally and might have solved this :-)

PS: The session() method works both client and server (normally) but I am thinking of adding a server-only method to that talks directly to the DB (or reads JWT cookie if that is enabled); it would need to be passed a DB connection though, so would be more awkward to use.

@iaincollins
Copy link
Member

Hey this MIGHT work for testing locally:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

Okay, found what the issue is.

Here's how I have things set up:

docker-compose has 2 containers (will have more once I set everything up):
app
nginx

only nginx is exposed and routes all requests wherever, and handles ssl.
app receives http requests, but all the https parts work thanks to nginx.

When the session gets requested by app itself, it uses baseUrl, which is https://localhost:3000. Since node doesn't have ssl configured, it errors

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

So, to fix this, I have some options:

  1. add https to app
  2. handle session verification without calling the https url
  3. Find a workaround

@iaincollins
Copy link
Member

iaincollins commented Jun 5, 2020

Oh neato!

So you can set process.env.NEXTAUTH_SITE to http://localhost:3000 URL and the session() method will use that.

When calling session() on the server, this env var just needs to be set before you call it, like this:

process.env.NEXTAUTH_SITE = "http://localhost:3000"

You can also set it in nextauth.config.js as a client env if you want the browser to do that, but in this case I don't think you need to do that, as it only needs to be set when called server side in your setup, I think?

@iaincollins
Copy link
Member

(Note in production everything should be fine; the example site has server side calls to session; it should just work normally when everything has a 'real' cert.)

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

Thanks!

By setting NEXTAUTH_SITE I now get this error as a response:

400 Bad Request
The plain HTTP request was sent to HTTPS port
nginx/1.19.0

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

So it looks like the best solution is to have a server-side method

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

I see that is server/session.js there is already a usage of getSession from the adapter.

Would it be sane to write a wrapper around that to get the session?

@iaincollins
Copy link
Member

Oh sure, I appreciate that is kludgy but you can totally just load the database provider directly and call methods from it.

e.g.

import Adapters from `next-auth/adapters`
const adapter = Adapters.Default(process.env.DATABASE_URL)
const db = await adapter.getAdapter({ /* options */ } )
const { getSession } = db

const sessionTokenCookieName = '__Secure-next-auth.session-token'
const sessionToken = req.cookies[sessionTokenCookieName]

// getSession(sessionToken)

BTW: Just checking if you are using JWT or database sessions?

If you are using JWT instead of database sessions, then you can get the session from the cookie, like this:

const jwtSecret = 'your secret' //  // see docs for basic options
const sessionTokenCookieName = '__Secure-next-auth.session-token' 
const sessionMaxAge =  30 * 24 * 60 * 60 * 1000 // see docs for basic options
const sessionToken = req.cookies[sessionTokenCookieName]
const token = jwt.verify(sessionToken, jwtSecret, { maxAge: sessionMaxAge })

However I think I would see if there is a way you can set NEXTAUTH_SITE to something other endpoint (just HTTP but on a different port) that the server can call?

Maybe there there an internal IP or hostname you could set it to that would mean it doesn't try to connect to the nginx instance?

I think as it only needs to be set for local development (and maybe in a test environment, if you have one) that would be easier.

@TwoAbove
Copy link
Contributor Author

TwoAbove commented Jun 5, 2020

Thanks @iaincollins !

Yeah, I'll try to get the redirect working before doing this.

@geraldnolan
Copy link
Contributor

@TwoAbove Can you share your nginx configuration?

@geraldnolan
Copy link
Contributor

@iaincollins and @iaincollins I was doing some additional testing to see if I could mimic the error @TwoAbove was getting and the same was happening using HAPROXY.

version: '3'

services:
  next-auth-example:
    build: 
     context:  ./
     dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - '3000:3000'
  haproxy:
    build: 
     context:  ./haproxy
     dockerfile: Dockerfile
    links:
      - next-auth-example 
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
      - 1936:1936

HAPROXY Config The configuration below gets an A+ on https://www.ssllabs.com/

Dockerfile

FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY /configs/ssl/private/localhost.pem  /usr/local/etc/private/localhost.pem

haproxy.cfg

global
		log 127.0.0.1 local0 notice
		maxconn 2000
 
defaults
		log global
		mode    http
		option  httplog
		option  dontlognull
		option   forwardfor
		option   http-server-close
		timeout connect 5000
		timeout client  50000
		timeout server  50000
 
listen  stats
        bind *:1936
        mode http
        stats enable
        stats hide-version
        stats realm Haproxy\ Statistics
        stats uri /

frontend www_frontend
		bind *:80     # Bind to port 80 (www) on the container
		reqadd X-Forwarded-Proto:\ http
		default_backend www-backend

frontend www-https
   bind *:443 ssl crt /usr/local/etc/private/localhost.pem no-sslv3 no-tlsv10 no-tls-tickets ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
   reqadd X-Forwarded-Proto:\ https
   default_backend www-backend

backend www-backend
   redirect scheme https if !{ ssl_fc }
   http-request redirect prefix http://%[hdr(host),regsub(^www\.,,i)] code 301 if { hdr_beg(host) -i www. }
   server www-1 next-auth-example:3000 check
   #server www-2 DOCKERHOST02:3000 check
   #server www-3 DOCKERHOST03:3000 check
   compression algo gzip



Error when running Next-Auth-Example

next-auth-example_1  | CLIENT_FETCH_ERROR https://localhost/api/auth/session FetchError: request to https://localhost/api/auth/session failed, reason: connect ECONNREFUSED 127.0.0.1:443
next-auth-example_1  |     at ClientRequest.<anonymous> (/usr/src/app/node_modules/next/dist/compiled/node-fetch/index.js:1:147710)
next-auth-example_1  |     at ClientRequest.emit (events.js:310:20)
next-auth-example_1  |     at TLSSocket.socketErrorListener (_http_client.js:426:9)
next-auth-example_1  |     at TLSSocket.emit (events.js:310:20)
next-auth-example_1  |     at emitErrorNT (internal/streams/destroy.js:92:8)
next-auth-example_1  |     at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
next-auth-example_1  |     at processTicksAndRejections (internal/process/task_queues.js:84:21) {
next-auth-example_1  |   type: 'system',
next-auth-example_1  |   errno: 'ECONNREFUSED',
next-auth-example_1  |   co

To get around this error on development.

  1. Create Certificate
openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
  1. Open up Keychain Access.
  2. Drag your certificate into Keychain Access.
  3. Go into the Certificates section and locate the certificate you just added
  4. Double click on it, enter the trust section and under “When using this certificate” select “Always Trust

@iaincollins iaincollins removed the bug Something isn't working label Jul 24, 2020
@jan-wilhelm
Copy link

PS: The session() method works both client and server (normally) but I am thinking of adding a server-only method to that talks directly to the DB (or reads JWT cookie if that is enabled).

Is there any update on this server-only method @iaincollins ? It would be super useful for us, as it would effectively half our Lambda invocations (every single API endpoint always needs to call the "/session" endoint, resulting in two Lambda invocations). Thank you! ❤️

@n-youngbunnies
Copy link

Anything here @iaincollins? We could really use a server-side getSession utility. It makes no sense to make another api request from our api just to get the session. We just need to load it directly from the db.

@balazsorban44
Copy link
Member

See a reasoning here: #947 (comment)

@jonasgroendahl
Copy link

Hey this MIGHT work for testing locally:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

This worked for me, im using a custom server with a self signed cert to get HTTPS on localhost :)

@Andrew-Gee
Copy link

I had to do add this

NEXTAUTH_URL_INTERNAL='http://127.0.0.1:3000'

Note that it it only seemed to work with the IP address. It did not work with 'localhost'

@benmarte
Copy link

benmarte commented May 2, 2022

I had to do add this

NEXTAUTH_URL_INTERNAL='http://127.0.0.1:3000'

Note that it it only seemed to work with the IP address. It did not work with 'localhost'

Thanks for posting this, it solved this issue with an app running on a kubernetes cluster 🙌 🎉

@asura6
Copy link

asura6 commented May 14, 2022

I did a short piece on how this problem can be solved using a reverse proxy together with your own root CA signed certificates. Maybe it can be of help to someone struggling with this.

https://risaksson.com/post/10/2022-05-13/HTTPS-enabled-development-environments

@Gr3at
Copy link

Gr3at commented Apr 4, 2024

@iaincollins and @iaincollins I was doing some additional testing to see if I could mimic the error @TwoAbove was getting and the same was happening using HAPROXY.

version: '3'

services:
  next-auth-example:
    build: 
     context:  ./
     dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - '3000:3000'
  haproxy:
    build: 
     context:  ./haproxy
     dockerfile: Dockerfile
    links:
      - next-auth-example 
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
      - 1936:1936

HAPROXY Config The configuration below gets an A+ on https://www.ssllabs.com/

Dockerfile

FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY /configs/ssl/private/localhost.pem  /usr/local/etc/private/localhost.pem

haproxy.cfg

global
		log 127.0.0.1 local0 notice
		maxconn 2000
 
defaults
		log global
		mode    http
		option  httplog
		option  dontlognull
		option   forwardfor
		option   http-server-close
		timeout connect 5000
		timeout client  50000
		timeout server  50000
 
listen  stats
        bind *:1936
        mode http
        stats enable
        stats hide-version
        stats realm Haproxy\ Statistics
        stats uri /

frontend www_frontend
		bind *:80     # Bind to port 80 (www) on the container
		reqadd X-Forwarded-Proto:\ http
		default_backend www-backend

frontend www-https
   bind *:443 ssl crt /usr/local/etc/private/localhost.pem no-sslv3 no-tlsv10 no-tls-tickets ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
   reqadd X-Forwarded-Proto:\ https
   default_backend www-backend

backend www-backend
   redirect scheme https if !{ ssl_fc }
   http-request redirect prefix http://%[hdr(host),regsub(^www\.,,i)] code 301 if { hdr_beg(host) -i www. }
   server www-1 next-auth-example:3000 check
   #server www-2 DOCKERHOST02:3000 check
   #server www-3 DOCKERHOST03:3000 check
   compression algo gzip

Error when running Next-Auth-Example

next-auth-example_1  | CLIENT_FETCH_ERROR https://localhost/api/auth/session FetchError: request to https://localhost/api/auth/session failed, reason: connect ECONNREFUSED 127.0.0.1:443
next-auth-example_1  |     at ClientRequest.<anonymous> (/usr/src/app/node_modules/next/dist/compiled/node-fetch/index.js:1:147710)
next-auth-example_1  |     at ClientRequest.emit (events.js:310:20)
next-auth-example_1  |     at TLSSocket.socketErrorListener (_http_client.js:426:9)
next-auth-example_1  |     at TLSSocket.emit (events.js:310:20)
next-auth-example_1  |     at emitErrorNT (internal/streams/destroy.js:92:8)
next-auth-example_1  |     at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
next-auth-example_1  |     at processTicksAndRejections (internal/process/task_queues.js:84:21) {
next-auth-example_1  |   type: 'system',
next-auth-example_1  |   errno: 'ECONNREFUSED',
next-auth-example_1  |   co

To get around this error on development.

  1. Create Certificate
openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
  1. Open up Keychain Access.
  2. Drag your certificate into Keychain Access.
  3. Go into the Certificates section and locate the certificate you just added
  4. Double click on it, enter the trust section and under “When using this certificate” select “Always Trust

You saved the day. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Ask how to do something or how something works
Projects
None yet
Development

No branches or pull requests