OAuth在本地及Chrome生产环境正常,Firefox/Safari无法使用
OAuth登录跨浏览器/移动端失效问题
我是Web开发新手,还请多多包涵!我将React应用部署在Netlify(域名:famtivity.fun),后端托管在Heroku。
最初Google/GitHub的OAuth仅在localhost环境正常工作。部署后,OAuth登录重定向回个人资料页时无法完成用户授权,但用户信息已成功写入数据库。在后端express session上方添加app.set("trust proxy", 1)后,Chrome浏览器恢复正常,但移动端和Firefox仍无法正常登录:用户被重定向到个人资料页,但未处于登录状态,不过用户信息依然能写入Heroku的JAWSDB数据库。
仓库链接:
- 前端:https://github.com/Lindsay-Renna/lindsay-renna-capstone
- 后端:https://github.com/Lindsay-Renna/lindsay-renna-capstone-api
后端配置代码
const app = express(); app.use( cors({ origin: process.env.CLIENT_URL, credentials: true, }) ); app.use(express.json()); app.use(helmet()); app.set("trust proxy", 1); app.use( expressSession({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true, cookie: { secure: process.env.NODE_ENV === "production", sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", }, }) ); // =========== Passport Config ============ // Initialize Passport middleware app.use(passport.initialize()); app.use(passport.session()); // Initialize GitHub strategy middleware passport.use( new GitHubStrategy( { clientID: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET, callbackURL: process.env.GITHUB_CALLBACK_URL, }, (_accessToken, _refreshToken, profile, done) => { console.log("GitHub profile:", profile); // Check for user in DB knex("users") .select("id") .where({ github_id: profile.id }) .then((user) => { if (user.length) { // If user is found, pass user object to serialize function done(null, user[0]); } else { // If user isn't found, create a record knex("users") .insert({ github_id: profile.id, google_id: null, avatar_url: profile._json.avatar_url, username: profile.username, }) .then((userId) => { // Pass the user object to serialize function done(null, { id: userId[0] }); }) .catch((err) => { console.log("Error creating a user", err); }); } }) .catch((err) => { console.log("Error fetching a user", err); }); } ) ); // Initialize Google strategy middleware passport.use( new GoogleStrategy( { clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: process.env.GOOGLE_CALLBACK_URL, }, (_accessToken, _refreshToken, profile, done) => { knex("users") .select("id") .where({ google_id: profile.id }) .then((user) => { if (user.length) { done(null, user[0]); } else { knex("users") .insert({ google_id: profile.id, github_id: null, avatar_url: profile.photos[0].value, username: profile.displayName, }) .then((userId) => { done(null, { id: userId[0] }); }) .catch((err) => { console.log("Error creating a user", err); }); } }) .catch((err) => { console.log("Error fetching a user", err); }); } ) ); passport.serializeUser((user, done) => { console.log("serializeUser (user object):", user); done(null, user.id); }); passport.deserializeUser((userId, done) => { console.log("deserializeUser (user id):", userId); // Query user information from the database for currently authenticated user knex("users") .where({ id: userId }) .then((user) => { console.log("req.user:", user[0]); done(null, user[0]); }) .catch((err) => { console.log("Error finding user", err); }); });
认证路由代码
// Github authentication routes router.get("/github", passport.authenticate("github")); router.get( "/github/callback", passport.authenticate("github", { failureRedirect: `${process.env.CLIENT_URL}/auth-fail`, }), (req, res) => { res.redirect(`${process.env.CLIENT_URL}/profile`); } ); // Google authentication routes router.get("/google", passport.authenticate("google", { scope: ["profile"] })); router.get( "/google/callback", passport.authenticate("google", { failureRedirect: `${process.env.CLIENT_URL}/auth-fail`, }), (req, res) => { // Successful authentication res.redirect(`${process.env.CLIENT_URL}/profile`); } ); // User profile endpoint that requires authentication router.get("/profile", (req, res) => { // If `req.user` isn't found send back a 401 Unauthorized response if (req.user === undefined) return res.status(401).json({ message: "Unauthorized 😒" }); // If user is currently authenticated, send back user info res.status(200).json(req.user); }); // Logout endpoint router.get("/logout", (req, res) => { req.logout((error) => { if (error) { return res.status(500).json({ message: "Server error, please try again later", error: error, }); } // Redirect the user back to client-side application res.redirect(process.env.CLIENT_URL); }); });
前端个人资料页认证检查代码
import { useState, useEffect } from "react"; const SERVER_URL = import.meta.env.VITE_APP_SERVER_URL; import axios from "axios"; import "./ProfilePage.scss"; const ProfilePage = ({ isLoggedIn, setIsLoggedIn }) => { const [movies, setMovies] = useState([]); const [isAuthenticating, setIsAuthenticating] = useState(true); useEffect(() => { authenticateUser(); }, []); const [profileData, setProfileData] = useState(null); const authenticateUser = async () => { try { const res = await axios.get(`${SERVER_URL}/auth/profile`, { withCredentials: true, }); setIsAuthenticating(false); setIsLoggedIn(true); console.log(res.data); setProfileData(res.data); localStorage.setItem("user_id", res.data.id); getWatchedList(res.data.id); } catch (err) { if (err.response && err.response.status == 401) { setIsAuthenticating(false); setIsLoggedIn(false); } else { console.log("Error authenticating", err); } } }; const getWatchedList = async (id) => { try { const res = await axios.get(`${SERVER_URL}/user/${id}/watched-list`); const movies = res.data.sort((a, b) => a.movie_name.localeCompare(b.movie_name) ); setMovies(movies); } catch (err) { console.log(err); } }; const handleRemoveMovie = (id) => { try { axios.delete(`${SERVER_URL}/user/${id}`); } catch (error) { console.log(err); } setMovies(movies.filter((movie) => movie.id !== id)); }; return ( <section className="profile-page"> {isLoggedIn ? ( profileData && ( <div className="profile-page__profile"> <h2 className="profile-page__header"> Hello, {profileData.username} </h2> <div className="profile-page__info"> <img className="profile-page__avatar" src={profileData.avatar_url} alt={`${profileData.username} avatar`} /> </div> <div className="profile-page__watch-list"> <DynamicTable movies={movies} handleRemoveMovie={handleRemoveMovie} /> </div> <div className="profile-page__logout-wrapper"> <LogoutButton setIsLoggedIn={setIsLoggedIn} /> </div> </div> ) ) : ( <div className="login-request"> <div className="login-request__container"> <h2 className="login-request__header"> Please login to access your profile </h2> <LoginButton /> </div> </div> )} </section> ); }; export default ProfilePage;
前端登录按钮代码
import "./LoginButton.scss"; const SERVER_URL = import.meta.env.VITE_APP_SERVER_URL; const LoginButton = () => { return ( <div className="sign-on-partners"> <a className="login-button" href={`${SERVER_URL}/auth/google`}> <div className="login-button__wrapper"> <img src="/icons/google.svg" alt="google logo" height="32" viewBox="0 0 16 16" version="1.1" width="32" /> <span className="login-button__text">Login with Google</span> </div> </a> <a className="login-button" href={`${SERVER_URL}/auth/github`}> <div className="login-button__wrapper"> <svg height="32" viewBox="0 0 16 16" version="1.1" width="32"> <path fillRule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" ></path> </svg> <span className="login-button__text">Login with GitHub</span> </div> </a> </div> ); }; export default LoginButton;
已尝试的解决方案
我曾调整Cookie设置为以下配置,但问题仍未解决:
app.set('trust proxy', 1); app.use(session({ secret: "secret", resave: false, saveUninitialized: false, proxy: true, cookie: { secure: "auto", sameSite: "none", }, }))
内容的提问来源于stack exchange,提问作者Lindsay Renna
相关产品推荐
相关产品推荐

