Express, Create-React-App, & passport-twitter - node.js

I am setting up Twitter oauth on my create-react-app application by using a helper function (via axios) on the front end to initiate the passport oauth process on my backend. I am currently in development, so I am hosting my express server on port 3001 and my front end on port 3000 with a proxy on the front end to port 3001. I have set up CORS permissions via the cors npm package.
No matter what set of configurations I try, I am unable to complete the Twitter OAuth process. I have tried switching ports, keeping the same ports; I have tried proxying my backend with express-http-proxy.
I have used http://127.0.0.1 instead of localhost in both my callback function and my initial api call, trying both ports 3000 and 3001.
I'm at the point where I'm not sure where I'm going wrong or if I need to abandon the passport-twitter for other solutions.
In every case, I keep getting the following error:
Failed to load https://api.twitter.com/oauth/authenticate?
oauth_token=alphanumericcoderedactedherebyme: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'http://localhost:3000' is
therefore not allowed access.
Depending on the configuration I have attempted, I get origin as null or http://localhost:3001 or http://127.0.0.1.
Note, I successfully call my backend api numerous times for other reasons, such as connecting to Yelp Fusion API. Moreover, I am using middleware to log my session data and I can see that I am successfully getting oauth_token and oauth_token_secret from Twitter. The call is failing on the next leg of the oauth process:
[0] *************SESSION MIDDLEWARE***************
[0] Session {
[0] cookie:
[0] { path: '/',
[0] _expires: 2018-01-06T20:20:31.913Z,
[0] originalMaxAge: 2678400000,
[0] httpOnly: true },
[0] 'oauth:twitter':
[0] { oauth_token: 'alphanumericcoderedactedherebyme',
[0] oauth_token_secret: 'alphanumericcoderedactedherebyme' } }
[0]
[0] Logged In:
[0] __________ false
[0] **********************************************
Here is relevant portions of my code -
BACKEND CODE
SERVER.JS
// Dependencies
const express = require("express");
const cors = require("cors");
const passport = require('passport');
// Initialize Express Server
const app = express();
// Specify the port.
var port = process.env.PORT || 3001;
app.set('port', port);
app.use(passport.initialize());
app.use(passport.session());
//enable CORS
app.use(cors());
//set up passport for user authentication
const passportConfig = require('./config/passport');
require("./controllers/auth-controller.js")(app);
// Listen on port 3000 or assigned port
const server = app.listen(app.get('port'), function() {
console.log(`App running on ${app.get('port')}`);
});
PASSPORT.JS
const passport = require('passport');
const TwitterStrategy = require('passport-twitter').Strategy;
passport.use(new TwitterStrategy({
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: process.env.NODE_ENV === 'production' ? process.env.TWITTER_CALLBACK_URL : 'http://localhost:3000/auth/twitter/callback'
},
function(accessToken, refreshToken, profile, done) {
...etc, etc, etc
AUTH-CONTROLLER.JS
const router = require('express').Router();
const passport = require('passport');
module.exports = function(app) {
router.get('/twitter', passport.authenticate('twitter'));
router.get('/twitter/callback',
passport.authenticate('twitter', {
successRedirect: '/auth/twittersuccess',
failureRedirect: '/auth/twitterfail'
})
);
router.get('/twittersuccess', function(req, res) {
// Successful authentication
res.json({ user: req.user, isAuth: true });
})
router.get('/twitterfail', function(req, res) {
res.statusCode = 503;
res.json({ err: 'Unable to Validate User Credentials' })
})
app.use('/auth', router);
}
FRONTEND CODE
HELPERS.JS
import axios from 'axios';
export function authUser() {
return new Promise((resolve, reject) => {
axios.get('/auth/twitter', {
proxy: {
host: '127.0.0.1',
port: 3001
}
}).then(response => {
resolve(response.data);
}).catch(err => {
console.error({ twitterAuthErr: err })
if (err) reject(err);
else reject({ title: 'Error', message: 'Service Unavailable - Please try again later.' });
});
});
}
UPDATE
I verified that Passport Authentication works on my backend port. I called the endpoint directly in the browser and was redirected to the Twitter auth which then returned to my callback the new user saved in my schema which was saved to the session data.
This means the problem lies with the use of Create-React-App on a different port than my backend.
http://127.0.0.1:3001/auth/twittersuccess
"user": {
"_id": "redactedbyme",
"name": "Wesley L Handy",
"__v": 0,
"twitterId": "redactedbyme",
"favorites": [],
"friends": []
},
"isAuth": true

I could not find a solution to the problem at hand, after consulting several developers and posting this question in other forums.
However, according to this blog, passport-twitter is not optimized for RESTful apis. This same blog provides a helpful tutorial for using this passport-twitter-token strategy together with react-twitter-auth found here
The issue relates to the fact that with Create-React-App the application runs on two different servers, one for the front-end and another for the back. There is no way around the CORS issue without a series of communications between the front-end and back-end, which passport does not allow. Passport is a great tool for handling OAuth on a single-server, but there is quite a bit of back and forth needed in OAuth that requires more complexity.
The tutorial by Ivan Vasiljevic is a helpful starting place for both understanding and working through that complexity.

If anyone coming here got stuck with fetching users with Reactjs from passport-twitter strategy, Here is the solution I found.
All you have to do is allowing credentials
Enabling credentials gives the ability to use cookies or express-session on the frontend. Read MDN for more details.
Back-End:
// cors policy setup
app.use(
cors({
origin: "http://localhost:3000", // front end url
optionsSuccessStatus: 200,
credentials: true,
})
);
Front-End:
axios.get(`${apiURL}/profile`, { withCredentials: true })

Related

Session Lost using nodejs express, cors, express-session

Working on a backend using nodejs, express, express-session, cors & cookie-parser to communicate with a react app that use axios to send http request, and using mariadb for the database.
Using both of them on localhost (3000 front end, 3001 backend) work fine, session is correctly saved and can be retrieved / used (I just pass the user data).
When deploying the backend on either local network or an EC2 instance from aws, the req.session return only the parameters set on "cookies" in the app.use(session({}) when called after being set on the login.
app.js:
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const pool = require('./config/database');
const cookieParser = require('cookie-parser');
const app = express();
app.use(express.json());
app.use(cors(
{
credentials: true,
origin: true,
}
));
app.set('trust proxy', 1)
app.use(cookieParser());
app.use(session({
secret: 'cat on keyboard',
saveUninitialized: false,
resave: false,
cookie: { httpOnly: false, maxAge: 1000 * 60 * 60 * 24 }
}));
The req.session set
getAccountIdByEmail: async (req, res) => {
// connect & retrieve user from credentials //
req.session.logged_user = user[0][0];
return res.status(200).json({ success: user[0][0] })
};
The axios call from react app:
const fetchData = () => {
if (adress.charAt(0) == '/') {
adress = endpoint + adress;
}
axios({
method: method,
url: adress,
data: content,
withCredentials: true
})
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
At first i thougt it came from Nginx on Aws EC2, but it does the same calling directly on port 3001 of the instance, then i had the same issue on a local network.
I've tried also to use a store (express-session-mariadb-store or express-mysql-session), without success.
I think it might be tied to cors or headers, but couldn't pinpoint what doesn't work.
I noticed on express-session-npm
there is a disclaimer saying it is only for development and will have memory leaks if deployed in production

Unable to access passport user in socket.io when using cors

I am creating a react app and I was adding functionality of registering users.
Everything was successful but I am unable to access Passport User property in socket I used the same code given in socket.io example
const session = require("express-session");
const passport = require("passport");
io.use(wrap(session({ secret: "cats" })));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error("unauthorized"))
}
});
This example works fine if domain is same but when I use CORS I am unable to access the passport property in session.
my react app domain is localhost:3000 and socket server domain is localhost:5000
Assuming that you are using same protocol and same domain but different ports it should still work fine if you setup your client and server with cors flags, e.g
// server-side
const io = new Server(httpServer, {
cors: {
origin: "https://example.com",
allowedHeaders: ["my-custom-header"],
credentials: true
}
});
// client-side
import { io } from "socket.io-client";
const socket = io("https://api.example.com", {
withCredentials: true,
extraHeaders: {
"my-custom-header": "abcd"
}
});
The sample above was taken from socket.io docs: https://socket.io/docs/v4/handling-cors/
However, the above configuration will work only if client/server are sharing the same top level domain and same protocol. e.g. client: https://example.com, server: https://server.example.com
I spent some time to figure out myself why:
client: http://127.0.0.1:3000 does not work with server: https://127.0.0.1:8000, notice the protocol difference.
With cors configurations in place, it works fine if I use http://127.0.0.1:8000 for server.
PS: If you need to use different top domains, be aware of SameSite policy that might be in place for your browser: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
This policy might restrict your cookies to be sent to server.
so... if different protocol or domains, you should make sure that you session cookie has SameSite flag set as 'none', via:
const session = require('express-session');
...
// Session setup
const sessionConfig = {
secret: 'secret', // Session secret
resave: false, //don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
cookie: {
sameSite: 'none',
secure: true,
}
}
const sessionMiddleware = session(sessionConfig);
app.use(sessionMiddleware);
...
io.use(wrap(sessionMiddleware));
both sameSite and secure properties are needed if you are playing with https:// protocol

I am having problem using http-proxy-middleware

I have two servers running in my backend, because of that I have to use the http-proxy-middleware package but I am encountering some problems.
This is my code in the frontend which is running on localhost:3000
axios("/api2/login",data)
.then((res) => {
});
This is my code in the backend which is running on localhost:5001
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use(createProxyMiddleware('/api2', {target: 'http://localhost:5001', changeOrigin: true}))
app.post("/login", (req, res, next) => {
res.send("Logged In");
});
This code is no working showing this error in the browser's console
GET http://localhost:3000/api2/login 404 (Not Found)
Uncaught (in promise) Error: Request failed with status code 404
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:61)
I am not able to understand where I am going wrong.
Looks like it's hitting localhost:3000 instead of localhost:5001 which is where your server is running from.
axios("http://localhost:5001/api2/login",data)
.then((res) => {
});
You can also set the baseURL in axios. HTTP get request with Axios gets sent with the local host IP at the beginning of the URL
If I understand correctly your server is listening on port 5001. So, you need to proxy your requests from 3000 to 5001. You can do that in the react application by setting the proxy value in package.json:
"proxy": "http://localhost:5001",
For more information about this subject, check the docs.
Edit for the configuration explained in the comment section
First in package.json:
"proxy": "http://localhost:5002",
Create a new server (proxyserver) which will be listening in port 5002:
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// redirection for first server 5000
app.use('/api1', createProxyMiddleware({ target: 'http://localhost:5000', changeOrigin: true }));
// redirection for second server 5001
app.use('/api2', createProxyMiddleware({ target: 'http://localhost:5001', changeOrigin: true }));
app.listen(5002);
For the other servers (5000 and 5001) you don't need to redirect the requests. Just make sure they're listening on the right ports (5000 and 5001), for example:
const express = require('express');
const app = express();
app.post("/api2/login", (req, res, next) => {
res.send("Logged In");
});
app.listen(5001);
I followed the steps mentioned in this post along with some changes,
I changed my Axios request code to:
axios({
method: "POST",
data: user,
withCredentials: true,
url: "/api2/login",
}).then((res) => {})
Otherwise, the proxy server was treating it as a GET request.
Secondly, I changed the proxy endpoint code int the proxy server as:
app.use('/api2', createProxyMiddleware({
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {
[`^/api2`]: '',
},
}));
Further information about the proxy endpoint change can be found here.

getting CORS and network error while calling axios post request using Vue js

I am getting error while calling axios post request. But it works properly on postman.
The code I used for calling the request is
methods : {
displayData(){
var config = {
method: 'post',
url: 'http://localhost:5000/api/request/displayRequest',
headers: {
'Content-Type': 'application/json'
},
data : JSON.parse(JSON.stringify(this.user._id))
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
},
async mounted(){
this.displayData()
}
I have already implemented CORS on the back-end in server.js
// Cors Middleware
const cors = require('cors');
app.use(cors());
app.options("*", cors());
app.use(
cors({
origin: (origin, callback) => callback(null, true), // you can control it based on condition.
credentials: true, // if using cookie sessions.
})
);
in your backend use this :
npm i cors
and in your express backend entrypoint:
const cors = require("cors");
app.use(cors());
app.options("*", cors());
You are running your front-end on localhost and using some port. Also, your back-end is running on localhost, port 5000. But your front-end application can not access any other port due to CORS policy. You can solve this problem in the back-end if you are using Node JS.
Install cors by the following command:
npm i cors
Then on your server file, change your app by
app.use(cors());
N.B. If you used React js, you could use http-proxy-middleware. Just create a file inside the src directory named "setupProxy.js". and add the following lines.
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000/",
})
);
};
Don't forget to change the port in this file into the port of your server.

Koa.js: ctx.setcookie() fails to set cookie

In my server.js code below I am setting up a middleware that should pass through Shopify OAuth and then redirect to the / route.
The '/' route, and its redirect url, are loaded in an iframe inside the shopify Admin area. I do see the page that / redirects to. But no cookies are present.
Related to the cookie settings, I am accessing this route in a web browser and on a secure https connection.
I am using Google Chrome Version 79.0.3945.88 (Official Build) (64-bit). I'm also using EditThisCookie browser extension to see the cookies that are present for the domain.
Can anyone tell why the cookies I am trying to set in server.js are failing to set?
import "isomorphic-fetch";
require("dotenv").config();
import Koa from "koa";
import Router from "koa-router";
import session from "koa-session";
import authorizeForShopify, {verifyRequest} from "#shopify/koa-shopify-auth";
const koa = new Koa();
const router = new Router();
const {SHOPIFY_BUYUSED_API_KEY, SHOPIFY_BUYUSED_API_SECRET, SHOPIFY_BUYUSED_SCOPES} = process.env;
koa.keys = [SHOPIFY_BUYUSED_API_SECRET];
koa.use(session({secure: true, sameSite: "none"}, koa));
////// Shopify OAuth //////
koa.use(authorizeForShopify({
apiKey : SHOPIFY_BUYUSED_API_KEY
, secret : SHOPIFY_BUYUSED_API_SECRET
, scopes : SHOPIFY_BUYUSED_SCOPES.split(",")
, afterAuth(ctx: Koa.Context): void {
console.log(`=====inside afterAuth()=====`); // I don't see this log statement
const {shop, accessToken} = ctx.session;
console.log({ // also I do not see this one
message : "from inside afterAuth()"
, shop
, accessToken
});
// cookie setting
const cookieOptions = {
httpOnly: true,
secure: true,
signed: true,
overwrite: true
};
// neither cookie is present in EditThisCookie
ctx.cookie.set("buyUsed_shopName", shop, cookieOptions);
ctx.cookie.set("buyUsed_generalToken", accessToken, cookieOptions);
ctx.redirect("/");
}
}));
////// Routing //////
router.get('/', async ctx => {
// ctx.body = "Koa server running, '/' route triggered"
ctx.redirect("https://storage.cloud.google.com/buy_used/consoleLog.js");
});
koa.use(verifyRequest());
koa.use(router.routes())
.use(router.allowedMethods());
const port: number = Number(process.env.PORT) || 8080;
koa.listen(port, undefined, undefined, () => console.log(`=====Koa listening on port ${port.toString()}=====`));
In the case of Koa, the methods to work with cookies are ctx.cookies.get and ctx.cookies.set. Thus, the lines should be changed to:
// neither cookie is present in EditThisCookie
ctx.cookies.set("buyUsed_shopName", shop, cookieOptions);
ctx.cookies.set("buyUsed_generalToken", accessToken, cookieOptions);
It works when setting, "secureProxy: true"
ctx.cookies.set('jwt', token, { httpOnly: true, secure: true, sameSite: "none", secureProxy: true });

Resources