-1

I'm using Next.js + react-apollo + apollo-server + express for my website. Recently I added cookie authentication and therefore had to enable CORS in the server for cookies auth to work. However I see that apollo client queries result in http 500 status when performed serverside. The same queries when performed clientside successfully resolve. I'm puzzled because I'd actually expect issues to happen on the client side because CORS has more impact there. I'm not sure what is causing the issue, any suggestions will be very welcome!

The error itself i as follows:

ApolloError: Network error: request to https://example.com/graphql/ failed, reason: write EPROTO 140152723232576:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1544:SSL alert number 40

    at new ApolloError (/src/node_modules/apollo-client/bundle.umd.js:92:26)
    at /src/node_modules/apollo-client/bundle.umd.js:1588:34
    at /src/node_modules/apollo-client/bundle.umd.js:2008:15
    at Set.forEach (<anonymous>)
    at /src/node_modules/apollo-client/bundle.umd.js:2006:26
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (/src/node_modules/apollo-client/bundle.umd.js:2004:20)
    at /src/node_modules/apollo-client/bundle.umd.js:1483:29
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  graphQLErrors: [],

networkError: FetchError: request to https://example.com/graphql/ failed, reason: write EPROTO 140152723232576:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1544:SSL alert number 40

I'm using an SSL certificate supplied by Amazon Cloudfront.

This is my client code:

_app.js:

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
      if (pageProps.errorStatusCode && ctx.res) {
        ctx.res.statusCode = pageProps.errorStatusCode;
      }
    }

    return { pageProps };
  }
  render() { //render... }
}

I have a HOC for page queries:

const withQuery = (Page, query, variables, errorPolicy = 'none') => {
  Page.getInitialProps = async ctx => {
    const { apolloClient } = ctx;
    try {
      const { data } = await apolloClient.query({
        query,
        variables: vars,
        errorPolicy
      });
      return { data };
    } catch (error) {
      return { errorStatusCode: error.networkError ? '500' : '404' };
    }
  };
  // if (typeof window === 'undefined') { // THIS CODE IS CAUSING THE ISSUE
  //   return Page;
  // }
}

This is how I initiate apollo client:

import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import introspectionQueryResultData from '../../fragmentTypes.json';

const fragmentMatcher = new IntrospectionFragmentMatcher({
  introspectionQueryResultData
});

function createClient({ ctx, headers, initialState }) {
  return new ApolloClient({
    credentials: 'include',
    uri: 'some_graphql_url',
    cache: new InMemoryCache({ fragmentMatcher }).restore(initialState || {}),
    headers
  });
}

export default withApollo(createClient, { getDataFromTree: 'ssr' });

This is my server code:

import cors from 'cors'
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { schema } = require('./models')
const server = new ApolloServer({
  schema,
})

// required settings to accept cookies
const corsOptions = {
  origin: function (origin, callback) {
    if (corsWhitelist.indexOf(origin) !== -1 || !origin) {
      callback(null, true)
    } else {
      callback(new Error(`${origin} - not allowed by CORS`))
    }
  },
  credentials: true
}

let app = express()
app.use(cors(corsOptions))
server.applyMiddleware({ app, cors: false })
const serverUrl = `my_server_url`
app.listen({ port }, () => console.log(`  Server ready at ${serverUrl}`))

To sum up my findings:

  1. The issue originates when _app.js calls await Component.getInitialProps(ctx).
  2. getInitialProps is defined in the withQuery HOC where the query is executed by apolloClient.query method.

Without CORS everything works as well.

EDIT: I noticed that the issue will start happening when headers option is added to createClient along with CORS.

EDIT2: the error occurs even without CORS, it's enough that headers option is added to createClient which create apollo client.

Yos
  • 1,276
  • 1
  • 20
  • 40
  • 1
    The only way that I know of to begin solving `500 Internal Server Error` is to go look at the server-side logfiles. – Mike Robinson Feb 19 '20 at 15:44
  • Does this answer your question? [Solving sslv3 alert handshake failure when trying to use a client certificate](https://stackoverflow.com/questions/36370656/solving-sslv3-alert-handshake-failure-when-trying-to-use-a-client-certificate) – EternalHour Feb 19 '20 at 17:57
  • @EternalHour no it doesn't. I never had SSL problems in the website so they only started when I added CORS. – Yos Feb 20 '20 at 10:05
  • The error message cited in the question seems to pretty clearly show that the actual problem you’re having in an SSL error. I don’t think anybody else here can guess why you might have started having that SSL problem after you “added CORS”, but it’s nevertheless an SSL problem, not a CORS problem. – sideshowbarker Feb 20 '20 at 11:12

2 Answers2

1

The reason I was getting the error is because I was passing all of the headers from serverside (express.js), including the host header to Amazon Cloudfront. Because my website was configured to use SNI it was important to pass the correct servername (this parameter is used to determine SSL certificate for which virtual host to serve from an IP address). It so happens that Apollo client uses node-fetch npm package for making http requests which in its turn uses https Node.js module. If host header exists then https sets the servername to the value of host, else servername gets the value of the hostname (mywebsite.com). So in my case the servername was bla.bla.elasticbeanstalk.com which of course resulted in SSL handshake error because the SSL certificate was for mywebsite.com. I wrote more information here.

Yos
  • 1,276
  • 1
  • 20
  • 40
  • Im getting the same error when building from vercel (using getStaticProps to fetch the data, which is SSG). Do you know how this fix may apply there? – alexr89 Jul 01 '20 at 00:31
  • the problem happened in my code because I was overwriting `headers` object when initializing `ApolloClient`. It wasn't related to build. You should check your headers and maybe read the link I wrote which explains more about the SNI error. – Yos Jul 01 '20 at 08:27
  • I did have a look, but dont think I can pass headers in getStaticProps to my apolloClient. Because getStaticProps runs at build time, it does not receive data that’s only available during request time, such as query parameters or HTTP headers as it generates static HTML. (https://nextjs.org/docs/basic-features/data-fetching) – alexr89 Jul 01 '20 at 09:56
-1

your code was incomplete


const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
  console.log(` Server ready at http://localhost:4000${server.graphqlPath}`)
);

Mr Coder
  • 507
  • 3
  • 13
  • I'm importing `schema` object in the file which contains typeDefs and resolvers. – Yos Feb 19 '20 at 16:26