I use passport.js to log in with Google account to my web app.
There is a login button if the user clicks on it then they can choose a Google account to log in, it works fine.
What I want is if no user is authenticated then an automatic redirect should be performed to login.
I have a React js front end which sends GET/POST/etc requests to node.js backend, so if nobody is logged in and a fetch is initiated then Google login page should be displayed automatically before executing anything else.
The error message in Chrome is (no user is logged in so the node.js backend tried to redirect to auth/google endpoint where passport.js tries to authenticate with Google):
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3500%2Fauth%2Fgoogle%2Fcallback&scope=profile&client_id=734494171389-f8kk3r9n7e3crt61ilpvmal20uku9qq4.apps.googleusercontent.com' (redirected from 'http://localhost:3500/repositories') from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
What interesting is that the same Google URL is called when I click on the button (it works) and when trying to redirect when user is not authenticated (it doesn't work -> CORS error):
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3500%2Fauth%2Fgoogle%2Fcallback&scope=profile&client_id=734494171389-f8kk3r9n7e3crt61ilpvmal20uku9qq4.apps.googleusercontent.com
Any thought please why I am having this CORS error only at redirect?
Thank you very much!
server.ts
...
const app: Express = express()
app.use(
session({
secret: [process.env.COOKIE_SECRET!],
cookie: {
secure: process.env.NODE_ENV === "production" ? true : false,
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
maxAge: 30 * 24 * 60 * 60 * 1000,
},
resave: false,
saveUninitialized: false,
})
)
app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
app.use(cors({
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true,
origin: process.env.CLIENT_URL!
}))
app.use("/auth", authRouter);
const ensureAuthenticated = (req: any, res: any, next: any) => {
if (req.isAuthenticated()) {
logger.info(`It's authenticated.`)
return next()
}
else {
logger.info(`It is not authenticated, redirecting to '/auth/google'.`)
res.redirect('/auth/google')
}
}
app.all('*', function(req,res,next) {
if (req.path === '/' || req.path.startsWith('/auth'))
next();
else
ensureAuthenticated(req,res,next);
});
// routes
app.use('/extractions', extractionRouter)
app.use('/prog-langs', progLangRouter)
app.use('/repositories', repositoryRouter)
app.use('/skills', skillRouter)
app.use('/developers', developerRouter)
app.listen(PORT, async () => {
await initDB()
logger.info(`Server running on port ${PORT}`)
})
auth.ts
...
passport.use(new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: 'http://localhost:3500/auth/google/callback',
},
async(accessToken, refreshToken, profile, done) => {
const userData = profile._json;
let user = {};
try {
const currentUserQuery = await AuthUserModel.findByPk(userData.sub)
if (currentUserQuery) {
user = {
user_id: currentUserQuery.id,
username: currentUserQuery.name,
}
} else {
const newAuthUser: AuthUserModel = await AuthUserModel.create({
id: userData.sub,
name: userData.name
})
user = {
user_id: newAuthUser.id,
username: newAuthUser.name
}
}
done(null, user);
} catch (error: any) {
done(error, false, error.message)
}
}
))
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
logger.info(`deserializeUser: ${JSON.stringify(user)}`)
done(null, user!)
})
const authRouter = express.Router()
authRouter.get("/google",
passport.authenticate("google", { scope: "profile", })
)
authRouter.get(
"/google/callback",
passport.authenticate(
"google",
{
successRedirect: process.env.CLIENT_URL!,
failureRedirect: "/auth/login/failed"
}
)
)
authRouter.get("/login/success", (req, res) => {
if (req.user) {
res.json({
success: true,
message: "user has successfully authenticated",
user: req.user,
cookies: req.cookies
});
}
});
// when login failed, send failed msg
authRouter.get("/login/failed", (req, res) => {
res.status(401).json({
success: false,
message: "user failed to authenticate."
});
});
// When logout, redirect to client
authRouter.post("/logout", (req, res, next) => {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect(process.env.CLIENT_URL!);
})
});
export default authRouter
Button on front end
export default function Login() {
const handleOAuth = () => {
window.open(`${endpointBackEnd}/auth/google`, "_self");
};
return (
<span>
<button onClick={handleOAuth}>
Login with Google
</button>
</span>
)
}
My solution was to return 401 in ensureAuthenticated
function and handles the 401 in React.