React, Graphql & Passport.js - node.js

I am trying to setup graphql with passport.js, everything seems to work fine on the server side, but on the client side when I do a check to see which user is currently logged in (req.user) I get undefined, while in server side, I do get the current user
I am running my server on localhost:4000 and client on localhost:3000.
some of my configs are as follows:
I have tried to change credentials on the client side as well as cors on the server side
Server config
app.use(
session({
resave: false,
saveUninitialized: false,
secret,
store: new MongoStore({
url: MONGO_URI,
autoReconnect: true
}),
cookie: {
maxAge: 1000 * 60 * 60 * 2
}
})
);
const server = new ApolloServer({
typeDefs,
resolvers,
// required for passport req.user access
playground: { settings: { 'request.credentials': 'include' } },
// so we have access to app req,res through graphql
context: ({ req, res }) => ({
req,
res
})
});
server.applyMiddleware({ app });
Client Config
const cache = new InMemoryCache();
const link = new HttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'same-origin',
});
const client = new ApolloClient({
cache,
link,
});
I am hoping to be able to access get the current logged in user on the client side (react)

Just in case anyone has the same issue, there are a couple of things we need to fix in order to make this work:
in client:
const link = createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
});
in Server:
// pass types and resolvers
const server = new ApolloServer({
typeDefs,
resolvers,
// required for passport req.user access
playground: { settings: { 'request.credentials': 'include' } },
// so we have access to app req,res through graphql
context: ({ req, res }) => ({
req,
res
})
});
server.applyMiddleware({
app,
cors: { origin: 'http://localhost:3000', credentials: true }
});
It worked for me :)

Related

Cookie not being sent to AWS ECS server from ReactJS

I am current deploying a MERN Stack application and have successfully deployed the backend api to http://44.198.159.229/. I am now trying to connect it to my client server which is still running on localhost:3000. However, I am running into a cookie related issue. I am receiving the cookie on my frontend from the backend express server, but upon making a get request an authenticated route the frontend is not sending the cookie back. In the network tag in google chrome I see that the cookie is instead filtered out. I have done countless research and browsed various posts but cannot seem to find the solution for this. It works when I check the api route manually in my browser but does not upon sending an axios request. It also works when I'm deploying the backend on my local server but I imagine because they are both on the same domain.
Here is my express configuration on the backend.
const corsOptions = {
credentials: true,
origin: "http://localhost:3000",
optionsSuccessStatus: 200,
};
// Express backend for web application
const app = express();
app.set("trust proxy", true);
/////////////////////////////////////////////////////// Middleware //////////////////////////////////////////////////
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(
session({
secret: "somethingsecretgoeshere",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: false,
secure: false,
maxAge: 10 * 60 * 100000,
sameSite: 'none'
},
})
);
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
passportConfig(passport);
app.use("/api", auth_routes);
app.use("/api", major_requirement_routes);
app.use("/api", user_course_routes);
export default app;
Here is the route at which I am making the get request to see if a user is authenticated
router.get("/auth/check", (req, res) => {
console.log(req.user)
console.log(req.cookies)
if (req.user) {
User.findOne({netId: req.user}, function (err, docs) {
if (err) {
console.log(err);
} else {
res.json({
auth: true,
user: req.user,
courseList: docs.courseList,
semesterList: docs.semesterList,
major: docs.major,
creditsApplied: docs.creditsApplied,
emailAddress: docs.emailAddress,
});
}
});
} else {
res.json({auth: false, id: null});
}
});
Here is my axios config
import axios from "axios";
const backend_url = "http://44.198.159.229:5000/api"
// const backend_url = "http://localhost:5000/api"
export default axios.create({
withCredentials: true,
baseURL: backend_url,
});
Here is my axios get request on the frontend
axios
.get("auth/check", { withCredentials: true,credentials: 'include',
})
.then(({ data}) => {
console.log(data)
if (data.auth) {
setIsAuthenticated(true);
setUser(data.user);
setCourseList(data.courseList);
setIsLoading(false);
} else {
setIsAuthenticated(false);
setCourseList(undefined);
setUser(undefined);
setIsLoading(false);
}
})
.catch(() =>
console.log(
"Something went wrong while trying to fetch your auth status."
)
);
}, []);
Okay so after a lot of research and playing around for a few days I have found a solution. I had to use a SSL and redirect traffic to an https server via AWS load balancing and set sameSite: None httpOnly: true, secure: true. I hope this helps someone else. This is because cookies can only be sent to cross origin sites that are secure. I also had to change my local host to run on https instead of http

Cookies are not being set in production

I deployed an express server to heroku as well as a next.js app.
The cookies are being sent from the server and even being shown in the network tab:
However, the cookies are not actually stored, all my requests failed because they depends on the csrf cookie, and the storage tab is empty:
This is the code for setting the cookies in the backend:
const csrfProtection = csrf({
cookie: {
httpOnly: true,
sameSite: 'none',
secure: true,
},
});
app.set('trust proxy', 1);
app.use(cors({ credentials: true, origin: clientOrigin }));
app.get('/', csrfProtection, function (req: Request, res: Response) {
res.cookie('XSRF-TOKEN', req.csrfToken(), { sameSite: 'none', secure: true });
res.end();
});
app.use(csrfProtection);
this is my axios instance:
const baseURL = process.env.baseURL;
const axiosInstance = Axios.create({
baseURL,
withCredentials: true,
});
and the request code:
useEffect(() => {
dispatch(loadingAction.setToTrue());
const getCsrf = async () => {
await axiosInstance.get('/');
};
getCsrf()
.then(() => {
dispatch(loadingAction.setToFalse());
})
.catch((err) => {
dispatch(loadingAction.setToFalse());
setError(err);
});
}, []);
While looking at your screenshot - I observed that the browser has a different domain set hilife01 vs hilife-1
Accessing throuugh https://hlife01.herokuapp.com/auth/login - gets you the cookies but your App doesn't have the right route configured.
Most likely, The Right domain is not associated with the cookie being set so while setting the cookie, the browser silently rejects the cookie because you are not matching the domain, however it is visible in the address bar.

Apollo client not connecting to server when Redis cookie is set

When I try to login which sets a cookie and then I refresh the page I don't get any response from apollo server and every requests made by graphql client are kept (pending) status.
After I remove a cookie, everything seems to work fine. I'm not even sure how can I debug this and have a little experience with backend so any advice would be helpful.
Here is how I setup connection from client:
const link = createHttpLink({
uri: 'http://localhost:5000/graphql',
credentials: 'include',
});
const apolloLink = ApolloLink.from([
errorLink,
link
]);
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: apolloLink,
});
And server:
useContainer(Container);
const establishDatabaseConnection = async (): Promise<void> => {
try {
await createDatabaseConnection();
} catch (error) {
console.error(error);
}
};
const initExpressGraphql = async () => {
const app = express();
const redis = new Redis();
const RedisStore = connectRedis(session);
const corsOptions = {
origin: 'http://localhost:3000',
credentials: true,
};
const schema = await buildSchema({
resolvers: RESOLVERS,
container: Container,
});
const apolloServer = new ApolloServer({
schema: schema as GraphQLSchema,
context: ({ req, res }: any) => ({ req, res }),
introspection: true,
plugins: [
ApolloServerLoaderPlugin({
typeormGetConnection: getConnection, // for use with TypeORM
}),
],
});
app.use(
session({
store: new RedisStore({
client: redis as any,
}),
name: 'rds',
secret: 'verysecretdata',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 1000 * 60 * 60 * 24 * 7 * 365, // 7 years
sameSite: 'lax',
},
})
);
apolloServer.applyMiddleware({
app,
cors: corsOptions
})
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
};
const startServer = async (): Promise<void> => {
await establishDatabaseConnection();
initExpressGraphql();
};
startServer();
My issue got solved after I restarted redis server

CSRF with fastify session cookies

I have a fastify session plugin that creates user sessions and manages them in postgres, but i want to make sure that i have all my sessions protected from CSRF. Im looking at the fastify-csrf plugin and im not exactly sure how to properly implement this. Do i need to generate the csrf token only when the session cookie is first generated or on all requests?
session plugin:
const cookie = require('fastify-cookie');
const session = require('fastify-session');
const csrf = require('fastify-csrf');
const pgSession = require('connect-pg-simple')(session);
const fp = require('fastify-plugin');
/**
* #param {import('fastify').FastifyInstance} fastify
*/
const plugin = async (fastify) => {
// All plugin data here is global to fastify.
fastify.register(cookie);
fastify.register(csrf, { sessionPlugin: 'fastify-session' });
fastify.register(session, {
store: new pgSession({
conString: process.env.DATABASE_URL,
tableName: 'user_session', // Defaults to 'session'
}),
secret: process.env.SESSION_SECRET,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 86400 * 1000, // 1 day expiration time
},
});
<!-- This is from the documentation, should this only be applied to the /login route when the cookie is generated? When do i verify that the cookie has not been tampered with?
fastify.route({
method: 'GET',
path: '/',
handler: async (req, reply) => {
const token = await reply.generateCsrf();
return { token };
},
});
// Add the user object to the session for later use.
fastify.addHook('preHandler', (req, reply, next) => {
if (!req.session) req.session.user = {};
next();
});
};
module.exports = fp(plugin);

Why passport authentication seems to work only in local?

I've a Node.js backend service and a React frontend. It was working till today when I had again an issue related to the CORS. It works fine in my local env but when I deploy this to App Engine the CORS issue is still there. What's is missing here?
Here my code:
Node.JS Backend Service:
const app = express();
/* MIDDLEWARE USER: set up cors to allow us to accept requests from our client */
app.use(
cors({
origin: process.env.CLIENT_URL || 'http://localhost:3001', // allow to server to accept request from different origin
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true, // allow session cookie from browser to pass through
}),
);
I'm using passport to obtain credentials from Google and pass to the server
/* MIDDLEWARE USE: use Session Middleware */
const MAX_AGE = process.env.MAX_AGE || 60 * 60 * 1000;
const SECRET = process.env.SECRET || 'Our Secret';
const DEFAULT_ENV = process.env.NODE_ENV || 'development';
app.use(session({
cookie: {
maxAge: MAX_AGE,
secure: DEFAULT_ENV === 'production',
// secure: true,
httpOnly: true,
},
secret: SECRET,
resave: false,
saveUninitialized: false,
// store: new FileStore(fileStoreOptions),
store: new FirestoreStore({
dataset: new Firestore({
kind: 'express-sessions',
}),
}),
}));
/* MIDDLEWARE USE: use Passport Middleware */
app.use(passport.initialize());
app.use(passport.session());
Then I use react & redux in my frontend and here the code to obtain credentials from my endpoint.
/* RETRIEVE INFO FROM OAUTH AS SOON USER CLICK ON LOGIN WITH GOOGLE */
export const loginWithGoogle = () => {
return (dispatch) => {
dispatch({type: FETCH_START});
axios.post('/auth/login/oauth/success').then(({data}) => {
// console.log('userSignInFromGoogle: ', data);
if (data) {
const {originalMaxAge} = data.session.cookie;
const expireDate = (new Date()).getTime() + originalMaxAge;
localStorage.setItem('token', JSON.stringify(data.result.accessToken));
localStorage.setItem('token_expires_in', JSON.stringify(expireDate));
axios.defaults.headers.common['Authorization'] = 'Bearer ' +
data.result.accessToken;
dispatch({type: FETCH_SUCCESS});
// dispatch({type: USER_DATA, payload: data.result});
dispatch({type: USER_TOKEN_SET, payload: data.result.accessToken});
} else {
dispatch({type: FETCH_ERROR, payload: data.error});
}
}).catch(function(error) {
dispatch({type: FETCH_ERROR, payload: error.message});
// console.log('Error****:', error.message);
});
};
};
/* FUNCTION TO FETCH DATA FROM THE AUTHENTICATED USER */
export const getAuthenticatedUser = () => {
return (dispatch) => {
dispatch({type: FETCH_START});
isTokenExpired();
axios.post('auth/me',
).then(({data}) => {
// console.log('userSignIn: ', data);
if (data.result) {
dispatch({type: FETCH_SUCCESS});
dispatch({type: USER_DATA, payload: data.result});
} else {
dispatch({type: FETCH_ERROR, payload: data.error});
}
}).catch(function(error) {
dispatch({type: FETCH_ERROR, payload: error.message});
// console.log('Error****:', error.message);
if (error) {
dispatch({type: SIGNOUT_USER_SUCCESS});
localStorage.removeItem('token');
localStorage.removeItem('token_expires_in');
}
});
};
};
Here where I define the endpoint for axios:
import axios from 'axios';
/* TODO: Change In production with this */
export default axios.create({
withCredentials: true,
baseURL: `backend-url`,//YOUR_API_URL HERE
headers: {
'Content-Type': 'application/json',
},
});
After some test to figure out what the problem could be here, I finally tested this code on various browser and then only Chrome showed this issue not passing the token from the backend server to the other server. In the end I modified the code snippet related to the session store, adding the "sameSite" property to the cookie. Chrome, in the latest version, requires this property to be specified, otherwise it blocks cookies from server to server.
/* MIDDLEWARE USE: use Session Middleware */
const MAX_AGE = process.env.MAX_AGE || 60 * 60 * 1000;
const SECRET = process.env.SECRET || 'Our Secret';
const DEFAULT_ENV = process.env.NODE_ENV || 'development';
app.use(session({
cookie: {
maxAge: MAX_AGE,
secure: DEFAULT_ENV === 'production',
httpOnly: true,
/*TODO: Fix for chrome*/
sameSite: 'none',
},
secret: SECRET,
resave: false,
saveUninitialized: false,
// store: new FileStore(fileStoreOptions),
store: new FirestoreStore({
dataset: new Firestore({
kind: 'express-sessions',
}),
}),
}));

Resources