How to use http-proxy-middleware with react and passport.js - reactjs

Well,I am working on a react app.I have an express server at localhost:5000 and the react app on localhost:3000.I am going through the google oauth flow using passportjs.Now the real problem is I have used the http-proxy-middleware to go to localhost:5000/auth/google from my react app using the log in button which directs to /auth/google.Then after authenticating I should be returned to the callbackURL which is /auth/google/callback.then from here i should be redirected to localhost:3000/surveys but I am getting redirected to localhost:5000/surveys which does not exist.So i am getting error:can not get /surveys.But when i log out using the logout button which directs to /api/logout then i am getting redirected to the homepage at '/' which is at localhost:3000.So why am i getting redirected to localhost:3000/ and not localhost:5000/ in this case.And how can i go to localhost:3000/surveys instead of localhost:5000/surveys as mentioned in the beginning?
//passport js file
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const mongoose = require("mongoose");
const keys = require("../config/keys");
const User = mongoose.model("users");
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user);
});
});
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback",
proxy: true,
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
return done(null, existingUser);
}
const user = await new User({ googleId: profile.id }).save();
done(null, user);
}
)
);
//the routes for my express server
const passport = require("passport");
module.exports = (app) => {
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google"),
(req, res) => {
res.redirect("/surveys");
}
);
app.get("/api/current", (req, res) => {
res.send(req.user);
});
app.get("/api/logout", (req, res) => {
req.logout();
res.redirect("/");
});
};
//the proxy setup in react src folder
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/auth/google",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
})
);
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
})
);
};
//my react header component
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
class Header extends React.Component {
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return (
<li>
Log In
</li>
);
default:
return (
<li>
Log Out
</li>
);
}
}
render() {
return (
<nav>
<div className="nav-wrapper">
<Link to={this.props.auth ? "/surveys" : "/"} className="brand-logo">
Emaily
</Link>
<ul className="right">{this.renderContent()}</ul>
</div>
</nav>
);
}
}
const mapStateToProps = (state) => {
return { auth: state.auth };
};
export default connect(mapStateToProps)(Header);

Well,just removing the change origin from proxy did the work!

Related

How to check if user is authenticated in React with express sessions and apollo on backend?

I have an app where I want to authenticate a user and show authenticated user pages which are unavailable to a non authenticated oneenter code here.
On backend I am using express-sessions and apollo-server.
Basically my idea was to set userId in the session, then check if it is empty or not and send from backend to frontend if user is authenticated or not.
My index.ts file is as following:
const { createClient } = require('redis');
let redisClient = createClient({ legacyMode: true });
async function main() {
const httpServer = http.createServer(app);
// check if userid exists
function isAuthenticated(req: any) {
const userId: string = req?.userId;
return !!userId;
}
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
await redisClient.connect();
app.use(
bodyParser.json(),
cors<cors.CorsRequest>(),
session({
store: new RedisStore({
host: host,
port: 6379,
client: redisClient,
}),
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
},
}),
expressMiddleware(server, {
context: async ({ req, res }) => {
return { req, res, isAuthenticated: isAuthenticated(req) };
},
})
);
try {
await mongoose.connect(process.env.MONGO_DB_URI || '');
console.log(`Mongoose connected on port`);
} catch (error) {
console.log(error);
}
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve)
);
console.log(`🚀 Server ready at http://localhost:4000/`);
}
main();
my sign-in resolver is this:
signInUser: async (_, { user }, context) => {
const { username, password } = user;
const foundUser = await UserModel.findOne({ username });
if (foundUser) {
const match = await bcrypt.compare(password, foundUser.password);
if (match) {
if (context.req.session) {
context.req.session.userId = foundUser.id; // here I am setting user id to the session
return { message: 'Signed in successfully!', status: 'success' };
}
}
}
},
and
Query: {
isAuthenticated: (_, __, {isAuthenticated}) => {
return isAuthenticated;
},
},
And on frontend I created a hook like this, to use in my components.
import { useQuery, gql } from '#apollo/client';
export const IS_AUTHENTICATED = gql`
query IsAuthenticated {
isAuthenticated
}
`;
const useAuth = () => {
const { data } = useQuery(IS_AUTHENTICATED);
return {
isAuthenticated: data && data.isAuthenticated,
};
};
export default useAuth;
The problem is that I req which I check userId does not have a userId yet in the apollo context. So it is always false. So I am not sure what approach would be better there?
I don't really want to set the cookie as httpOnly: false and manipulate it on frontend, for example.

Set interval gets clear on browser refresh in my react application

I am using the setInterval function to do an API call to get refresh token after some interval. Every time if I refresh the browser setInterval timer gets clear and starts counting from zero. My access token gets expired and the refresh token never getting a call and user logged out. Is there any way to deal with this?
useEffect(() => {
const interval = setInterval(() => {
setTokenByReSendingAuth(); //dispatch action
}, 300000);
return () => clearInterval(interval);
}, [setTokenByReSendingAuth]);
Using MERN:
Heres your dependencies:
jwt-decode: https://www.npmjs.com/package/jwt-decode
passport-jwt: http://www.passportjs.org/packages/passport-jwt/
passport: https://www.npmjs.com/package/passport
jsonwebtoken: https://www.npmjs.com/package/jsonwebtoken
bcryptjs: https://www.npmjs.com/package/bcryptjs
Create an express server.js like this:
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const path = require("path");
const passport = require("passport");
const db = require("./config/.env").mongoURI; //uri in .env file
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
mongoose.connect(db, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
});
const connection = mongoose.connection;
connection.once("open", () => {
console.log("MongoDB database connection established successfully");
});
const myRouter = require("./routes/example.js");
app.use(passport.initialize()); // used to attatch token to request headers
require("./config/passport")(passport);
app.use("/example", myRouter);
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
});
}
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
Install your dependencies:
"dependencies": {
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
"concurrently": "^5.3.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.11.8",
"passport": "^0.4.1",
"passport-jwt": "^4.0.0",
"validator": "^13.5.2",
"nodemon": "^2.0.7"
},
Add this to your package.json in whatever directory your server is in:
"devDependencies": {
"nodemon": "^2.0.7"
},
"scripts": {
"start": "node server.js",
"server": "nodemon server.js"
},
Now to the Auth part:
Make a config folder for your passport and URI:
In a .env file:
module.exports = {
mongoURI: "mongodb+srv://",
secretOrKey: "abunchofrandomcharacterscreatedwithbcrypt",
};
Make a passport.js file:
This adds the user's token to all request headers, it is automatically running since we used it in our server.js file.
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const mongoose = require("mongoose");
const User = mongoose.model("users");
const keys = require("./.env");
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;
module.exports = (passport) => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => console.log(err));
})
);
};
Make a middleware folder for your backend:
Add an auth.js file:
const jwt = require("jsonwebtoken");
const config = require("../config/.env").secretOrKey;
function authUser(req, res, next) {
const authHeader = req.header("Authorization");
const token = authHeader && authHeader.split(" ")[1];
// Check for token
if (!token)
return res.status(401).json({ msg: "No token, authorization denied" });
try {
// Verify token
const decoded = jwt.verify(token, config);
// Add user from payload
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: "Token is not valid" });
}
}
module.exports = {
authUser,
};
This file is attached to your routes in the header, like this:
router.post("/example/get", authUser, (req, res) => {
const { reqData } = req.body; //dont ever put user ids in here
.catch((err) => {
res.status(400).json({ msg: err });
});
});
The route to login and register should look like this:
const router = require("express").Router();
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
const { authUser } = require("../middleware/auth"); //used in the header of auth needed requests
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const keys = require("../config/.env");
const validateRegisterInput = require("../validation/register"); //checks and validates user register inputs
const validateLoginInput = require("../validation/login"); //checks and validates user register inputs
const User = require("../models/user");
const { Permissions } = require("../models/permissions");
//uses a middleware to validate register inputs, checks if user data exists in db, salts and hashes the password.
router.post("/register", (req, res) => {
const { errors, isValid } = validateRegisterInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
User.findOne({ email: req.body.email }).then((user) => {
if (user) {
return res.status(400).json({ email: "Email already exists" });
} else {
const newUser = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then((user) => res.json(user))
.catch((err) => console.log(err));
});
});
}
});
});
//login creds are req through this route, the details are compared to the db user collection, and the user data that matches the decoded password and username will be responed back through the token.
router.post("/login", (req, res) => {
const { errors, isValid } = validateLoginInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
const email = req.body.email;
const password = req.body.password;
User.findOne({ email }).then((user) => {
if (!user) {
return res.status(404).json({ email: "Email not found" });
}
bcrypt.compare(password, user.password).then((isMatch) => {
if (isMatch) {
const payload = {
id: user.id,
firstName: user.firstName,
};
jwt.sign(
payload,
keys.secretOrKey,
{
expiresIn: 31556926, //expires in a year
},
(err, token) => {
res.json({
success: true,
token: "Bearer " + token,
});
}
);
} else {
return res
.status(400)
.json({ passwordincorrect: "Password incorrect" });
}
});
});
});
module.exports = router;
That's basically it for the backend auth routing side of things, but for the client to get there token in the browser you need to add this stuff to the client:
In your index.js add this outside a component to run on every render no matter what:
This checks to see if there's a jwttoken in the browser, it decodes it and sets the user data into the state to be used globally. It also redirects the user.
import setAuthToken from "./utils/setAuthToken";
import jwt_decode from "jwt-decode";
if (localStorage.jwtToken) {
// Set auth token header auth
const token = localStorage.jwtToken;
setAuthToken(token);
// Decode token and get user info and exp
const decoded = jwt_decode(token);
// Set user and isAuthenticated
store.dispatch(setCurrentUser(decoded)); // using redux, can easily also just use contextApi or something else
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Redirect to login
window.location.href = "./";
}
}
Create login, register and logout functions:
import axios from "axios";
import setAuthToken from "../../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import { SET_CURRENT_USER } from "./authTypes"; //puts user data into state
import { showSnackbar } from "../inventory/inventoryActions";
export const registerUser = (userData) => (dispatch) => {
axios
.post("/users/register", userData)
.then(() => {
console.log("logged in")
})
.catch(() => {
console.log("something wrong")
});
};
export const loginUser = (userData) => (dispatch) => {
axios
.post("/users/login", userData)
.then((res) => {
const { token } = res.data;
localStorage.setItem("jwtToken", token);
setAuthToken(token);
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
dispatch(showSnackbar(`Successfully signed in!`, "success", 3000));
})
.catch(() => {
console.log("somethings wrong")
});
};
export const setCurrentUser = (decoded) => { // used in loginUser
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
//removes token from localstorage
export const logoutUser = () => {
return (dispatch) => {
localStorage.removeItem("jwtToken");
setAuthToken(false);
dispatch(setCurrentUser({}));
};
};
If you have any private components you only want logged in users to access use this PrivateRoute Component wrapper:
This redirects any user not logged in to the home page
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={(props) =>
auth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/" />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
});
export default connect(mapStateToProps)(PrivateRoute);
Use it as a react-router-dom element:
<PrivateRoute exact path="/example" component={privateComponentExample} />
If you have any questions, let me know. :)

How can I maintain accessToken from Google OAuth?

I am trying to implement Google OAuth with MERN Stack.
There was a nice blog about this like as below.
https://medium.com/#alexanderleon/implement-social-authentication-with-react-restful-api-9b44f4714fa
I used react-google-login for the front-end and passport, passport-google-token, and jsonwebtoken for the back-end.
It was all good until express js got the accessToken and created a User document, but I actually don't know what is the next step to keep login and authenticate it. This question is mentioned in the above article, but he hasn't implemented it.
Is there any information to solve this?
"Step 7: Return a JWT token to the frontend. What you do with that token is out of scope for this tutorial, but it should probably be used to authenticate each of the logged-in user’s actions."
My react side code is as below
import React from 'react';
import GoogleLogin from 'react-google-login';
import api from '../config/api';
import { useGlobalState } from '../config/globalState';
import { AUTH_SIGN_IN, SET_USER } from '../config/types';
const Navbar = () => {
const { state, dispatch } = useGlobalState();
const { isLoggedIn } = state;
const responseGoogle = async (data) => {
try {
const { accessToken } = data;
const res = await api.post('/api/auth/signin', {
access_token: accessToken
});
console.log('res.data:', res.data);
dispatch({ type: AUTH_SIGN_IN });
} catch (err) {
console.log('err: ', err.message);
}
};
return (
<div>
This is the title
{
!isLoggedIn &&
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Login with Google"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
}
</div>
)
}
export default Navbar
This is passport.js
const passport = require('passport');
const GooglePlusTokenStrategy = require('passport-google-token').Strategy;
const User = require('../models/user');
module.exports = passport => {
passport.use('googleToken', new GooglePlusTokenStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}, async (accessToken, refreshToken, profile, done) => {
console.log('accessToken:', accessToken);
console.log('refreshToken:', refreshToken);
console.log('profile:', profile);
try {
const foundUser = await User.findOne({ googleId: profile.id})
if (foundUser) {
console.log('User already exists');
return done(null, foundUser);
} else {
const newUser = await User.create({
googleId: profile.id,
displayName: profile.displayName,
email: profile.emails[0].value,
photo: profile.photos[0].value
});
await newUser.save();
return done(null, newUser);
}
} catch(error) {
done(error, false, error.message);
}
}));
}
This is the /api/auth/signin route handling part.
const JWT = require('jsonwebtoken');
const User = require('../models/user');
const signToken = user => {
return JWT.sign({
iss: 'AnySecret',
sub: user.id,
iat: new Date().getTime(),
exp: new Date().setDate(new Date().getDate() + 1)
}, process.env.JWT_SECRET);
}
module.exports = {
signIn: async (req, res) => {
console.log('signIn called');
const token = signToken(req.user);
res.cookie('access_token', token, {
httpOnly: true
});
res.status(200).send({
user: req.user,
token
});
}
}

How to use Auth0 with react-admin?

I'm trying to implement authentication using Auth0 in a react-admin v3 app. I need to implement an authProvider that talks with Auth0. This sounds like something that should be available somewhere, but the closest I could find was https://github.com/alexicum/merge-admin/blob/master/src/Auth/index.js, which is about 2 years old (the SDKs have changed since then).
Is there an Auth0 authProvider somewhere I can reuse, or do I have to implement it myself?
Thanks!
for reference sake, here's an example of a way to integrate react admin with auth0-react package
index.js
import { Auth0Provider } from "#auth0/auth0-react";
ReactDOM.render(
<Auth0Provider
domain="XXXXX.auth0.com"
clientId="XXXXX"
audience="https://XXXXX"
redirectUri={window.location.origin}
>
<React.StrictMode>
<App />
</React.StrictMode>
</Auth0Provider>,
document.getElementById("root")
);
App.js
import { withAuth0, withAuthenticationRequired } from "#auth0/auth0-react";
import ApolloClient from "apollo-boost";
// I'm using Hasura w/ JWT Auth, so here's an example of how to set Authorization Header
async componentDidMount() {
const token = await this.props.auth0.getAccessTokenSilently();
const client = new ApolloClient({
uri: "https://HASURA_URL/v1/graphql",
headers: {
Authorization: `Bearer ${token}`
},
});
buildHasuraProvider({ client }).then((dataProvider) =>
this.setState({ dataProvider })
);
}
export default withAuthenticationRequired(withAuth0(App));
I've created a sample application with Auth0 and react-admin way of auth
https://github.com/spintech-software/react-admin-auth0-example
Here is auth provider code for reference
import authConfig from "./authConfig";
import {Auth0Client} from '#auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: authConfig.domain,
client_id: authConfig.clientID,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
const CallbackURI = "http://localhost:3000/login"
export default {
// called when the user attempts to log in
login: (url) => {
if (typeof url === 'undefined') {
return auth0.loginWithRedirect({
redirect_uri: CallbackURI
})
}
return auth0.handleRedirectCallback(url.location);
},
// called when the user clicks on the logout button
logout: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) { // need to check for this as react-admin calls logout in case checkAuth failed
return auth0.logout({
redirect_uri: window.location.origin,
federated: true // have to be enabled to invalidate refresh token
});
}
return Promise.resolve()
})
},
// called when the API returns an error
checkError: ({status}) => {
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) {
return Promise.resolve();
}
return auth0.getTokenSilently({
redirect_uri: CallbackURI
})
})
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => {
return Promise.resolve()
},
};
My answer is following react-admin approach where I use its authProvider like below. There are two main steps:
Get needed data from useAuth0 hook.
Convert authProvider into function where it takes the above values, and return an object like default.
// In App.js
import authProvider from './providers/authProvider';// my path is changed a bit
const App = () => {
const {
isAuthenticated,
logout,
loginWithRedirect,
isLoading,
error,
user,
} = useAuth0();
const customAuthProvider = authProvider({
isAuthenticated,
loginWithRedirect,
logout,
user,
});
return (
<Admin
{...otherProps}
authProvider={customAuthProvider}
>
{...children}
</Admin>
);
}
// My authProvider.js
const authProvider = ({
isAuthenticated,
loginWithRedirect,
logout,
user,
}) => ({
login: loginWithRedirect,
logout: () => logout({ returnTo: window.location.origin }),
checkError: () => Promise.resolve(),
checkAuth: () => (isAuthenticated ? Promise.resolve() : Promise.reject()),
getPermissions: () => Promise.reject('Unknown method'),
getIdentity: () =>
Promise.resolve({
id: user.id,
fullName: user.name,
avatar: user.picture,
}),
});
export default authProvider;
That's it.
It's more convenient to wrap the react-admin app with auth0 native login, and then provide react-admin dataProvider an http client that reads the jwt token stored in local storage by auth0.

Problem Redirecting after google signin in express react app using passport-google-oauth20

I have express backend and create-react-app2 as frontend , I am using setupProxy also. Now I have configured the app for google sign in however I am not getting proper redirect to index page after signin. Here is google oauth setup in console.developer.google.com
I am using passport google oauth20 for authentication:
Here my passport file:
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const keys = require('./../keys/secret');
const {User} = require('./../models/user');
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ 'google.id' : profile.id });
if(existingUser) {
done(null, existingUser);
}else {
const user = await new User({
'google.id' : profile.id,
isSocialAccountPresent: true })
.save();
done(null, user);
}
}
));
}
Here are my routes:
router.get('/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get('/google/callback',
passport.authenticate('google'),
(req, res) => {
// Successful authentication, redirect home.
res.redirect('/');
});
Here is my setupProxy file:
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(proxy("/auth/*", { target: "http://localhost:5000/" }));
app.use(proxy("/api/*", { target: "http://localhost:5000/" }));
};
Im gettting redirected to following URL :
http://localhost:3000/auth/google/callback?code=4/0gBVQE1R0m7FWFlg_QtXE2n9lvwVHoG_lNNH_zCueOeQAJZc6-8FygiH9u_9BfnQQt8yl2ECmD1gW3Gq9by25D4&scope=email+profile+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email
instead of http://localhost:5000/auth/google/callback
In your setupProxy.js file try this...
app.use(proxy("/auth/**", { target: "http://localhost:5000/" }));
app.use(proxy("/api/*", { target: "http://localhost:5000/" }));
You'll see I added an additional asterisk. This tells node to go one level deeper for "/callback".
Using res.redirect('/') you've to use the full path (http://localhost:5000....).
None of the answers here has worked for me. I believe that you would already have the file setupProxy.js in the src/ folder of your React client. Then, in that file, if you're setting changeOrigin: true for the object passed in the createProxyMiddleware function, you should remove that and that should fix the problem. At least, that worked for me.
This did the trick for me on
setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = (app) => {
app.use(
["/api", "/auth/google/callback"],
createProxyMiddleware({
target: "http://localhost:5000",
})
);
};

Resources