apollo-server-express CORS issue - node.js

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: 'http://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.

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: 'http://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}`);
})

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: 'http://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

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 = [
"http://site1.com",
"https://site2.com"
];
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error("Not allowed by CORS"))
}
}
}
});

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

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 http://localhost:4000${server.graphqlPath}`);
}

Related

Tried everything I find but I can't solve CORS issue with Express server

I'm trying to fetch data from my Express API but I'm keep getting this error.
Firefox Console Error
Chrome Console Error
I have tried everything I find on internet.
I tried using different browser, browser extensions to bypass CORS check.
I thought maybe the issue is related to localhost, so I deployed it, but the same issue persisted.
I tried mockup API with the same frontend, and it fetches data just fine.
I tried manually adding Access-Control-Allow-Origin header on server side but did not work.
I also tried CORS middleware for Express and again did not work.
I'm getting proper responses with Postman just fine, but not within a browser.
This is my code on client side:
async create(visit) {
this.setState({visits: [...this.state.visits, {...visit}]})
fetch('http://localhost:8000/create-visit', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: `{"params": ${JSON.stringify(visit)}}`
})
.then(resolve => resolve.json())
.then(result => {console.log(result)})
}
I also tried GET request with but no difference.
And this is my server side code:
const express = require('express')
const visitRouter = require('./routers/visits')
const cors = require('cors')
const PORT = process.env.PORT;
const app = express();
var corsOptions = {
credentials: true,
origin: "*",
preflightContinue: true,
allowedHeaders: 'GET,PUT,POST,DELETE,OPTIONS'
}
var logger = function(req, res, next) {
console.log(res)
next()
}
app.use(logger)
app.use(express.json())
app.use(visitRouter)
// app.use((req, res, next) => {
// res.append('Access-Control-Allow-Origin', ['*'])
// res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
// res.append('Access-Control-Allow-Headers', 'Content-Type')
// next()
// })
// app.use(cors())
app.use(cors(corsOptions))
app.listen(PORT, () => {
console.log(`Listening from port: ${PORT}`)
})
Router:
const express = require('express')
const router = new express.Router()
const visitModel = require('../models/visits')
router.post('/create-visit', async (req, res) => {
try {
const params = req.body.params
console.log(params)
const newVisit = await visitModel.createVisit(params)
res.status(201).send(newVisit)
} catch (err) {
res.status(500).send(err)
}
})

Reciving empty file objects from frontend to backend React and NodeJS

Hi I'm trying to upload a file to a server send with axios. To send it I use react with Hooks and UseState, the thing is that when I do the console.log of the file in de frontend it shows all correctly but when I send it to backend I recive it empty.
Here is an example about what shows the frontend with console.log():
Here is the function I use to send the 3 files to backend and the differents things like react Hooks and that which I need:
const [weight, setWeight] = useState("");
const [frontPhoto, setFrontPhoto] = useState({});
const [sidePhoto, setSidePhoto] = useState({});
const [backPhoto, setBackPhoto] = useState({});
const JWT = new ClassJWT();
const axiosReq = axios.create();
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = async (e) => {
e.preventDefault();
await JWT.checkJWT();
console.log(frontPhoto);
axiosReq.post("http://localhost:3001/upload-progress", {
weight,
frontPhoto,
sidePhoto,
backPhoto,
token: JWT.getToken()
}).then((response) => {
console.log(response);
if (response.data.statusCode === '200') {
} else {
}
});
};
And then, in the backend de console.log() is like this:
{
weight: '70',
frontPhoto: {},
sidePhoto: {},
backPhoto: {},
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjI2NTk3Mjg1LCJleHAiOjE2MjY1OTgxODV9.njDz7BZX57NvAK399abQLhoelpTS4kStj4LBzjw5gR8'
}
Here is the router code I use to this upload:
routerProgress.post("/upload-progress", verifyJWT, async (req, res) => {
console.log(req.body);
}
And here is all the server configuration:
import express from 'express';
import sequelize from './db/db.js';
import cors from 'cors';
import fileUpload from 'express-fileupload';
// SERVER CONFIGURATION
const app = express();
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Listening at ${PORT}`);
sequelize.sync({ force: false })
.then(() => console.log('Database Connected!'))
.catch(err => console.log(err));
});
// BODYPARSER
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({ limit: '50mb', extended: true, parameterLimit: 50000}));
app.use(cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true
}));
app.use(fileUpload({
createParentPath: true
}));
// ROUTES
import { routerAuthentication } from './routes/authentication.js';
import { routerProgress } from './routes/progress.js';
app.use(routerAuthentication);
app.use(routerProgress);
I don't know how to solve it, I tried many things but anything doesn't word. Please if anyone know what can I do to solve it, I'll be very grateful with him. Thanks!

How can I solve the Access-Control-Allow-Origin CORS error in my MERN app for MSAL auth?

I'm trying to authenticate through MSAL in my MERN app by clicking a button.
However I get this error :
Access to XMLHttpRequest at
'https://login.microsoftonline.com/common/oauth2/v2.0/a...'
(redirected from 'http://<SERVER_URL>/api/auth/signin') from origin
'http://<CLIENT_URL>' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
Here is the code of my NodeJS server :
const express = require("express");
const session = require('express-session');
const authRoutes = require("./routes/auth.routes");
const msal = require('#azure/msal-node');
const cors = require("cors");
require("dotenv").config();
const app = express();
const corsOptions = {
origin : process.env.CLIENT_URL,
credentials: true,
"allowedHeaders": ["sessionId", "Content-Type"],
"exposedHeaders": ["sessionId"],
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false
}
app.use(cors(corsOptions));
// Demo only
app.locals.users = {};
// MSAL Config
const msalConfig = {
auth: {
clientId: process.env.OAUTH_APP_ID,
authority: process.env.OAUTH_AUTHORITY,
clientSecret: process.env.OAUTH_APP_SECRET
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
app.locals.msalClient = new msal.ConfidentialClientApplication(msalConfig);
// Session middleware
app.use(session({
secret: 'your_secret_value_here',
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));
app.use("/api/auth", authRoutes);
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(process.env.PORT, () => {
console.log(`Server is running on port ${process.env.PORT}`);
});
module.exports = app;
Here are my auth.controller methods :
module.exports = {
signIn: async (req, res) => {
const urlParameters = {
scopes: process.env.OAUTH_SCOPES.split(','),
redirectUri: process.env.OAUTH_REDIRECT_URI
};
try {
const authUrl = await req.app.locals.msalClient.getAuthCodeUrl(urlParameters);
res.redirect(authUrl);
} catch (error) {
console.log(`Error: ${error}`);
res.redirect("/");
}
},
callback: async (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: process.env.OAUTH_SCOPES.split(","),
redirectUri: process.env.OAUTH_REDIRECT_URI
};
try {
const response = await req.app.locals.msalClient.acquireTokenByCode(tokenRequest);
req.session.userId = response.account.homeAccountId;
const user = await graph.getUserDetails(response.accessToken);
req.app.locals.users[req.session.userId] = {
displayName: user.displayName,
email: user.mail || user.userPrincipalName,
timeZone: user.mailboxSettings.timeZone
};
} catch (error) {
console.log(`Error: ${error}`);
}
res.redirect("/");
},
signOut: async (req, res) => {
if (req.session.userId) {
const accounts = await req.app.locals.msalClient.getTokenCache().getAllAccounts();
const userAccount = accounts.find(a => a.homeAccountId === req.session.userId);
if (userAccount) {
req.app.locals.msalClient.getTokenCache().removeAccount(userAccount);
}
}
req.session.destroy(err => res.redirect("/"));
}
};
And here is the React part :
import React from 'react';
import axios from "axios";
const App = () => {
const handleConnect = () => {
axios({
method: "get",
url: `${process.env.SERVER_URL}/api/auth/signin`,
withCredentials: true
})
.then(res => console.log(res.data))
.catch(err => console.log(err));
};
return (
<button onClick={handleConnect}>Connect</button>
);
};
export default App;
In my Azure Active Directory admin center, my redirection URIs are :
"<CLIENT_URL>" as "SPA"
"<SERVER_URL>/api/auth/signin" as "Web"
The Network tab in devtools helps troubleshoot this sort of thing.
You probably need to handle CORS preflight requests, by putting something like this in your express app to handle OPTIONS requests.
app.options('*',cors())
Put this line before app.use() for any routes.
This one bit me in production. Ouch!
Setting Access-Control-Allow-Origin to * is very risky and not recommended. It means that you are allowing any origin to receive a response back from your server.
removing CORS means that Same Origin Policy will be enforced, therefor it won't work.
To solve the issue between your client and server, what you can do is set a proxy in your package.json file of the React app, which will point to your server: "proxy": "YourServerURI".
Regarding the initial question of the error from MSAL, I would suggest to double check that your app is registered correctly and has the permission to access your server.

Unable to link backend API routes - NextJS

I am making a shopify app and have created a Koa + NodeJS backend and NextJS running in frontend, in the same port, and also I have created a custom server.js file When running locally in my PC, its running all right, all the routes work as expected, and I am able to fetch data from Frontend (React) from the routes defined in my backend (Koa).
Now the problem is that, when I deploy my app to vercel it doesn't seem to be recognizing my Koa routes, and is throwing a 404 error in the console for all the requests I make to the backend routes. This is my first time working with NextJS, so I have really very little idea on what is wrong here, so I would like some support on this please.
Also when I deploy it, the shopify auth also doesn't seem to be working anymore like it does when its running in local development.
My Code:
server.js:
require('isomorphic-fetch');
const dotenv = require('dotenv');
dotenv.config();
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth');
const { verifyRequest } = require('#shopify/koa-shopify-auth');
const session = require('koa-session');
const { default: graphQLProxy } = require('#shopify/koa-shopify-graphql-proxy');
const { ApiVersion } = require('#shopify/koa-shopify-graphql-proxy');
const Router = require('koa-router');
const { receiveWebhook, registerWebhook } = require('#shopify/koa-shopify-webhooks');
const getSubscriptionUrl = require('./server/getSubscriptionUrl');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const {
SHOPIFY_API_SECRET_KEY,
SHOPIFY_API_KEY,
HOST,
} = process.env;
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.use(session({ sameSite: 'none', secure: true }, server));
server.keys = [SHOPIFY_API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_products', 'write_products'],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});
const registration = await registerWebhook({
address: `${HOST}/webhooks/products/create`,
topic: 'PRODUCTS_CREATE',
accessToken,
shop,
apiVersion: ApiVersion.October19
});
if (registration.success) {
console.log('Successfully registered webhook!');
} else {
console.log('Failed to register webhook', registration.result);
}
await getSubscriptionUrl(ctx, accessToken, shop);
}
})
);
router
.get('/api', ctx => {
ctx.res.statusCode = 200;
ctx.body = "API Route"
})
const webhook = receiveWebhook({ secret: SHOPIFY_API_SECRET_KEY });
router.post('/webhooks/products/create', webhook, (ctx) => {
console.log('received webhook: ', ctx.state.webhook);
});
server.use(graphQLProxy({ version: ApiVersion.April19 }));
router.get('*', verifyRequest(), async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I found out that Vercel isn't supporting custom servers anymore, if I right. from this https://github.com/vercel/next.js/issues/9397#issuecomment-556215227
So, I used Heroku to deploy my app instead, and now it is working all right.

React intercepts /auth/twitter/when HTTPS. I want to access /auth/twitter/ from my node server not the react application

New to React/Node.
I have an implementation of React(React-boiler-plate with webpack etc)/Node running on the same host on Heroku.
I am using passport and twitter-oath sessions.
When I hit the endpoint http://example.heroku.com/auth/twitter/callback everything works accordingly (as well as running local dev server).
When I try to access it via HTTPS https://example.heroku.com/auth/twitter/callback React intercepts it and shows a page not found page.
I am trying to get an understanding to understand why this happens and the best way to handle this in a "production" like environment. I would like to be handle /auth/twitter and /auth/twitter/callback all on the same host.
I have tried adding http proxy in misc places as well as package.json and to no avail I am spinning my wheels.
Thank you in advance.
auth routes
module.exports = app => {
app.get('/api/logout', (req, res) => {
// Takes the cookie that contains the user ID and kills it - thats it
req.logout();
// res.redirect('/');
res.send(false);
// res.send({ response: 'logged out' });
});
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get(
'/auth/twitter/callback',
passport.authenticate('twitter', {
failureRedirect: '/'
}),
(req, res) => {
res.redirect('/dashboard');
}
);
app.get('/api/current_user', (req, res) => {
// res.send(req.session);
// res.send({ response: req.user });
res.send(req.user);
});
};
index.js
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(
//
cookieSession({
// Before automatically expired - 30 days in MS
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.COOKIE_KEY]
})
);
app.use(passport.initialize());
app.use(passport.session());
require('./routes/authRoutes')(app);
// They export a function - they turn into a function - then immediately call with express app object
app.use('/api/test', (req, res) => {
res.json({ test: 'test' });
});
setup(app, {
outputPath: resolve(process.cwd(), 'build'),
publicPath: '/',
});
// get the intended host and port number, use localhost and port 3000 if not provided
const customHost = argv.host || process.env.HOST;
const host = customHost || null; // Let http.Server use its default IPv6/4 host
const prettyHost = customHost || 'localhost';
/ Start your app.
app.listen(port, host, async err => {
if (err) {
return logger.error(err.message);
}
// Connect to ngrok in dev mode
if (ngrok) {
let url;
try {
url = await ngrok.connect(port);
} catch (e) {
return logger.error(e);
}
logger.appStarted(port, prettyHost, url);
} else {
logger.appStarted(port, prettyHost);
}
});
console.log('Server listening on:', port);
/**
* Front-end middleware
*/
module.exports = (app, options) => {
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
const addProdMiddlewares = require('./addProdMiddlewares');
addProdMiddlewares(app, options);
} else {
const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
const addDevMiddlewares = require('./addDevMiddlewares');
addDevMiddlewares(app, webpackConfig);
}
return app;
};
const path = require('path');
const express = require('express');
const compression = require('compression');
module.exports = function addProdMiddlewares(app, options) {
// messing around here
const proxy = require('http-proxy-middleware');
const apiProxy = proxy('/api', { target: 'http://localhost:3000' });
const apiProxy2 = proxy('/auth', { target: 'http://localhost:3000' });
app.use('/api', apiProxy);
app.use('/auth/*', apiProxy2);
const publicPath = options.publicPath || '/';
const outputPath = options.outputPath || path.resolve(process.cwd(), 'build');
// compression middleware compresses your server responses which makes them
// smaller (applies also to assets). You can read more about that technique
// and other good practices on official Express.js docs http://mxs.is/googmy
app.use(compression());
app.use(publicPath, express.static(outputPath));
app.get('*', (req, res) =>
res.sendFile(path.resolve(outputPath, 'index.html')),
);
};
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
function createWebpackMiddleware(compiler, publicPath) {
return webpackDevMiddleware(compiler, {
logLevel: 'warn',
publicPath,
silent: true,
stats: 'errors-only',
});
}
module.exports = function addDevMiddlewares(app, webpackConfig) {
const compiler = webpack(webpackConfig);
const middleware = createWebpackMiddleware(
compiler,
webpackConfig.output.publicPath,
);
app.use(middleware);
app.use(webpackHotMiddleware(compiler));
// Since webpackDevMiddleware uses memory-fs internally to store build
// artifacts, we use it instead
const fs = middleware.fileSystem;
app.get('*', (req, res) => {
fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
if (err) {
res.sendStatus(404);
} else {
res.send(file.toString());
}
});
});
};
Chances are you have a service worker that is running client side and intercepting the requests, then serving your react app from it's cache.
One hint that gives it away is that the service worker will only be installed / run over https https://developers.google.com/web/fundamentals/primers/service-workers/#you_need_https
Solution would be to either edit the service worker code to have it not serve for the auth urls or disable it all together if you are not planning to build an app around it, it may be more trouble than it is worth.

Resources