apollo-server-express CORS issue

apollo-server-express CORS issue


25

So I am migrating to apollo-server-express 2.3.3 ( I was using 1.3.6 )
I’ve followed several guides, making the necessary tweaks but am stuck in a CORS issue.

According to the docs you have to use the applyMiddleware function to wire up the apollo server with express.

I am currently doing the following:

const app = express();

// CORS configuration

const corsOptions = {
    origin: 'https://localhost:3000',
    credentials: true
}

app.use(cors(corsOptions))

// Setup JWT authentication middleware

app.use(async (req, res, next) => {
    const token = req.headers['authorization'];
    if(token !== "null"){
        try {
            const currentUser = await jwt.verify(token, process.env.SECRET)
            req.currentUser = currentUser
        } catch(e) {
            console.error(e);
        }
    }
    next();
});

const server = new ApolloServer({ 
    typeDefs, 
    resolvers, 
    context: ({ req }) => ({ Property, User, currentUser: req.currentUser })
});

server.applyMiddleware({ app });


const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
})

For some reason my express middleware doesn’t seem to be executing, when I try to do a request from localhost:3000 (client app) I get the typical CORS error

With apollo-server-express 1.3.6 I was doing the following without no issues:

app.use(
    '/graphql',
    graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }),
    bodyParser.json(),
    graphqlExpress(({ currentUser }) => ({
        schema,
        context: {
            // Pass Mongoose models
            Property,
            User,
            currentUser
        }
    }))
);

Now with the new version, event though the docs make this look like a straightforward migration, I don’t seem to be able to make it work. I’ve checked various articles and no one seems to be having the issue.

6 Answers
6


45

From my understanding of the Apollo Server middleware API, CORS options, body-parser options and the graphql endpoint are treated as special entities that must be passed directly to the applyMiddleware param object.

So you want to try the following configuration:

const app = express();

// CORS configuration
const corsOptions = {
    origin: 'https://localhost:3000',
    credentials: true
}

// The following is not needed, CORS middleware will be applied
// using the Apollo Server's middleware API (see further below)
// app.use(cors(corsOptions))

// Setup JWT authentication middleware
app.use(async (req, res, next) => {
    const token = req.headers['authorization'];
    if(token !== "null"){
        try {
            const currentUser = await jwt.verify(token, process.env.SECRET)
            req.currentUser = currentUser
        } catch(e) {
            console.error(e);
        }
    }
    next();
});

const server = new ApolloServer({ 
    typeDefs, 
    resolvers, 
    context: ({ req }) => ({ Property, User, currentUser: req.currentUser })
});

// There is no need to explicitly define the 'path' option in
// the configuration object as '/graphql' is the default endpoint
// If you planned on using a different endpoint location,
// this is where you would define it.
server.applyMiddleware({ app, cors: corsOptions });

const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
})

7

  • 2

    I think this should be the accepted answer. It's the only answer that worked for me.

    – Davey

    Mar 21, 2019 at 11:28

  • The latest version of apollo-server (2.4.8) will throw an error when applyMiddleware method is called: "To use Apollo Server with an existing express application, please use apollo-server-express"

    – Karel Frajták

    Apr 25, 2019 at 11:56

  • 1

    @KarelFrajták yes, the applyMiddleware method is provided by apollo-server-{integration} packages as stated by apollographql.com/docs/apollo-server/api/…

    – Thomas Hennes

    Apr 26, 2019 at 12:20

  • 2

    @KarelFrajták to be clear, I didn't mention the need for that package as the OP stated he WAS using apollo-server-express, which provides the method

    – Thomas Hennes

    Apr 26, 2019 at 12:25

  • @Jaxx I just run into similar issue and also there was confusion on respective GitHub pages

    – Karel Frajták

    Apr 27, 2019 at 14:29


8

With Apollo Server 2.x you supply the cors field in the constructor of ApolloServer.

So in your case, it should look like the following:

const corsOptions = {
    origin: 'https://localhost:3000',
    credentials: true
}

// Setup JWT authentication middleware

app.use(async (req, res, next) => {
    const token = req.headers['authorization'];
    if(token !== "null"){
        try {
            const currentUser = await jwt.verify(token, process.env.SECRET)
            req.currentUser = currentUser
        } catch(e) {
            console.error(e);
        }
    }
    next();
});

const server = new ApolloServer({ 
    typeDefs, 
    cors: cors(corsOptions),
    resolvers, 
    context: ({ req }) => ({ Property, User, currentUser: req.currentUser })
});

server.applyMiddleware({ app });


const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
})

Here you find all params accepted by the apollo server:
https://www.apollographql.com/docs/apollo-server/api/apollo-server.html#Parameters-2

Here you find the relevant discussion:
https://github.com/apollographql/apollo-server/issues/1142


8

The CORS settings come from ExpressJS, not from ApolloServer. If you want to add a custom or wildcard origin you have to handle it with a callback/handler function.

const server = new ApolloServer({
    ....,
    cors: {
        credentials: true,
        origin: (origin, callback) => {
            const whitelist = [
                "https://site1.com",
                "https://site2.com"
            ];

            if (whitelist.indexOf(origin) !== -1) {
                callback(null, true)
            } else {
                callback(new Error("Not allowed by CORS"))
            }
        }
    }
});


8

By default, the express middleware will instantiate cors middleware with default options on the graphql path, overriding any cors middleware configuration you yourself have specified for other paths(!)

You can override the defaults when you apply the apollo middleware, e.g.

apollo.applyMiddleware({ app, cors: {credentials: true, origin: true} })

I’m using apollo-server-express 2.17


0

Just remove csrfPrevention: true and you are good to go

import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';

async function startApolloServer(typeDefs, resolvers) {
  // Required logic for integrating with Express
  const app = express();
  // Our httpServer handles incoming requests to our Express app.
  // Below, we tell Apollo Server to "drain" this httpServer,
  // enabling our servers to shut down gracefully.
  const httpServer = http.createServer(app);

  // Same ApolloServer initialization as before, plus the drain plugin
  // for our httpServer.
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    csrfPrevention: true,
    cache: 'bounded',
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  // More required logic for integrating with Express
  await server.start();
  server.applyMiddleware({
    app,

    // By default, apollo-server hosts its GraphQL endpoint at the
    // server root. However, *other* Apollo Server packages host it at
    // /graphql. Optionally provide this to match apollo-server.
    path: '/',
  });

  // Modified server startup
  await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve));
  console.log(`🚀 Server ready at https://localhost:4000${server.graphqlPath}`);
}


0

Something not mentioned in the previous answers is if you want to apply the same CORS policy globally where you have separate Express routes (e.g., /auth, /status, etc.) and a distinct /graphql endpoint.

You can keep the app.use(cors(corsOptions)) statement near the top of your code as the OP did and just remove the route-level policy. As shown below:

const app = express();

// CORS configuration
const corsOptions = {
    origin: 'https://localhost:3000',
    credentials: true
}

// Keep this as it applies the CORS policy "globally"
app.use(cors(corsOptions))


// Setup JWT authentication middleware
app.use(async (req, res, next) => {
    const token = req.headers['authorization'];
    if(token !== "null"){
        try {
            const currentUser = await jwt.verify(token, process.env.SECRET)
            req.currentUser = currentUser
        } catch(e) {
            console.error(e);
        }
    }
    next();
});

// Your other express routes
app.get('/auth', (req, res) => {
    res.status(200).json({ isLoggedIn: !!req.currentUser })
});

app.get('/status', (_, res) => {
    res.status(200).json({ message: "All good!" })
});

const server = new ApolloServer({ 
    typeDefs, 
    resolvers, 
    context: ({ req }) => ({ Property, User, currentUser: req.currentUser })
});

// Remove the route-level cors policy as it's now no longer needed
server.applyMiddleware({ app });

const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
})

Remember, the applyMiddleware function under-the-hood operates like any other express-style middleware i.e app.use()

In the latest version of Apollo Server (v4) this becomes very clear as the middleware is now applied like this:

server.use(
    '/graphql',
    cors(corsOptions),
    express.json(),
    expressMiddleware(apolloServer, {
      context: ({ req, res }) => {
        const { user } = req;
        return { res, user };
      }
    })
  );



Leave a Reply

Your email address will not be published. Required fields are marked *