reactjs - 使用 React/Nodejs/Passport 的 Google 身份验证/授权的 COR 问题
问题描述
我遇到了与 issue CORs Error: Google Oauth from React to Express (PassportJs validation)相同的问题。但我无法让@Yazmin 提供的解决方案起作用。
我正在尝试使用 Google 身份验证和授权创建一个 React、Express/Nodejs、MongoDB 堆栈。我目前正在使用 Vs Code 在 Windows 10 上开发堆栈(在 'localhost:3000 上反应,在 localhost:5000 上的 Nodejs 和在 localhost:27017 上的 MongoDB。
该应用程序的目的是使用谷歌地图、谷歌照片 API 和谷歌 Gmail API 在地图上显示城市草图(图像)。我将来可能还需要类似的访问 Facebook 群组的权限才能访问 Urban Sketches。但是现在我只包含了 profile 和 Email 的授权范围。
我想将所有对第三方资源的请求保留在后端,因为在架构上我理解这是最佳实践。
来自 http://localhost:5000 的谷歌授权过程运行良好并返回预期结果。但是,当我尝试从客户端执行相同操作时 - 源 Http://localhost:3000 在第一次尝试访问 google auth2 api 之后在开发人员工具控制台中返回以下错误。虽然 scheme 和 domain 相同但 port 不同,所以来自第三部分(https://account.google.com)的消息被浏览器拒绝了。
访问 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email%20https%3A %2F%2Fmail.google.com%2F&client_id='(从 'http://localhost:3000/auth/google' 重定向)来自原点 'http://localhost:3000' 已被 CORS 策略阻止:无“访问权限” -Control-Allow-Origin' 标头存在于请求的资源上。如果不透明的响应满足您的需求,请将请求的模式设置为“no-cors”以获取禁用 CORS 的资源。
无论我尝试什么,错误信息都是一样的。
我认为谷歌正在将回复发送到客户端(localhost:3000)而不是服务器。
在其他解决方案中,我尝试通过引用实现 Yilmaz 的解决方案:“在 client/src 中创建 setupProxy.js 文件。无需在任何地方导入。create-react-app 将查找此目录”我之前已经通过运行 create-react-app 创建了我的客户端。所以我在我的 src 文件夹中添加了 setupProxy.js。
问题:我认为我是正确的,在我重新启动客户端后,包含我的设置的新 setupProxy.cjs 文件将包含在 webpack 中。
在我看来,我得到的流程不是 BROWSER ==> EXPRESS ==> GOOGLE-SERVER 而是 BROWSER ==> EXPRESS ==> GOOGLE-SERVER ==>BROWSER 并在上面显示的 cors 错误中停止。
为了测试这个理论,我在 client\node_modules\http-proxy-middleware\lib\index.js 函数“shouldProxy”和“middleware”中放置了一些控制台日志消息,但无法检测到来自 auth/google 端点的任何活动来自谷歌授权服务器响应(https://accounts.google.com/o/oauth2/v2/auth)。
所以我认为我的理论是错误的,我不知道我将如何让它发挥作用。
在从 React 客户端向 /auth/google 端点发出请求后,在 VsCode 终端上显示的控制台日志消息如下...
http-proxy-middleware - 92 HttpProxyMiddleware - shouldProxy
context [Function: context]
req.url /auth/google
req.originalUrl /auth/google
Trace
at shouldProxy (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:96:13)
at middleware (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:49:9)
at handle (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-server\lib\Server.js:322:18)
at Layer.handle [as handle_request] (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:317:13)
at C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:284:7
at Function.process_params (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:335:12)
at next (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:275:10)
at goNext (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:28:16)
at processRequest (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:92:26)
http-proxy-middleware - 15 HttpProxyMiddleware - prepareProxyRequest
req localhost
The Google callback uri is http://localhost:5000/auth/google/callback
这是我的 nodejs 服务器代码的列表。
dotenv.config();
// express
const app = express();
// cors
app.use(cors())
// passport config
require ('./config/passport')(passport)
// logging
if( process.env.NODE_ENV! !== 'production') {
app.use(morgan('dev'))
}
const conn = process.env.MONGODB_LOCAL_URL!
/**
* dbConnection and http port initialisation
*/
const dbConnnect = async (conn: string, port: number) => {
try {
let connected = false;
await mongoose.connect(conn, { useNewUrlParser: true, useUnifiedTopology: true })
app.listen(port, () => console.log(`listening on port ${port}`))
return connected;
} catch (error) {
console.log(error)
exit(1)
}
}
const port = process.env.SERVERPORT as unknown as number
dbConnnect(conn, port)
//index 02
// Pre Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }))
const mongoStoreOptions = {
mongoUrl: conn,
collectionName: 'sessions'
}
app.use(
session({
secret: process.env.SESSIONKEY as string,
resave: false,
saveUninitialized: false,
store: MongoStore.create(mongoStoreOptions),
})
)
app.use(passport.initialize())
app.use(passport.session())
// Authentication and Authorisation
const emailScope: string = process.env.GOOGLE_EMAIL_SCOPE as string
//GOOGLE_EMAIL_SCOPE=https://www.googleapis.com/auth/gmail/gmail.compose
const scopes = [
'profile',
emailScope
].join(" ")
app.get('/auth/google', passport.authenticate('google', {
scope: scopes
}));
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/'}),
(req, res) => {
res.send('Google Login Successful ')
}
)
app.get('/', (req, res) => {
res.send('Hello World');
})
http-proxy-middleware setupProxy.cjs 文件。注意 cjs 扩展。我认为这是因为我使用的是 Typescript。它位于客户端 src 文件夹中
const createProxyMiddleware = require('http-proxy-middleware');
module.exports = function (app) {
app.use(createProxyMiddleware('/auth', {target: 'http://localhost:5000'}))
}
最后是来自客户端的 fetch 命令
async function http(request: RequestInfo): Promise<any> {
try {
const response = await fetch('/auth/google')
const body = await response.json();
return body
} catch (err) { console.log(`Err SignInGoogle`) }
};
还有护照配置...
import { PassportStatic} from 'passport';
import {format, addDays} from 'date-fns'
import { IUserDB, IUserWithRefreshToken, ProfileWithJson} from '../interfaces/clientServer'
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../models/User')
module.exports = function (passport:PassportStatic) {
const clientID: string = process.env.GOOGLE_CLIENTID as string
const clientSecret: string = process.env.GOOGLE_SECRET as string
const callbackURL: string = process.env.GOOGLE_AUTH_CALLBACK as string
const strategy = new GoogleStrategy(
{
clientID: clientID,
clientSecret: clientSecret,
callbackURL: callbackURL,
proxy: true
},
async (_accesstoken: string, _refreshtoken: string,
profile: ProfileWithJson,
etc
解决方案
您无法对 /auth/google 路由进行 fetch 调用!
这是我在javascript中的解决方案...
// step 1:
// handler function should use window.open instead of fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
说明....
步骤 1 将在您的浏览器上加载您的服务器身份验证 URL 并发出身份验证请求。
第 2 步然后在身份验证完成后在浏览器上重新加载客户端 url。
第 3 步使 api 端点可用于收集用户信息以更新反应状态
第 4 步调用端点,获取数据并更新用户状态。
推荐阅读
- python - 如何以编程方式在终端命令上编写预期输入?
- python - 如何获取数据集中某些单词的值计数
- javascript - 如何从 nodeJS 中的 PdfTron 签名文档中获取注释?
- sql - SQL将列除以另一个表中的数字
- c - 我怎样才能得到
文件并修复错误“致命错误:linux/bootmem.h:没有这样的文件或目录#include “? - r - R:将列表中每个数据框的行重新组织为新数据框的新列表
- python - 为网页抓取项目合并两个列表
- sql - 如何执行Rails中不存在的条件
- r - R Studio 成功连接到 Azure SQL 数据库,但表名是行话
- dns - 在 DNS 迭代查询中跟踪 A 记录的引用时,下一个问题应该是 A 类型还是 NS 类型?