OAuth 弹窗跨域安全 React.js
OAuth popup cross-domain security React.js
我对如何使用弹出窗口在 React 中实现 OAuth 很感兴趣 (window.open
)。
例如我有:
mysite.com
— 这是我打开弹出窗口的地方。
passport.mysite.com/oauth/authorize
— 弹出窗口。
主要问题是如何在 window.open
(弹出窗口)和 window.opener
之间创建连接(众所周知,由于跨域安全性,window.opener 为空,因此我们可以'不要再使用它了)。
⇑ window.opener
is removed whenever you navigate to a different host (for security reasons), there is no way around it. The only option should be doing the payment in a frame if it is possible. The top document needs to stay on the same host.
方案:
可能的解决方案:
- 使用
setInterval
描述 here 检查打开的 window。
使用 cross-storage(恕我直言,不值得)。
那么 2019 年推荐的最佳方法是什么?
Wrapper for React - https://github.com/Ramshackle-Jamathon/react-oauth-popup
由 Khanh TO. OAuth popup with localStorage. Based on react-oauth-popup 建议。
方案:
代码:
oauth-popup.tsx:
import React, {PureComponent, ReactChild} from 'react'
type Props = {
width: number,
height: number,
url: string,
title: string,
onClose: () => any,
onCode: (params: any) => any,
children?: ReactChild,
}
export default class OauthPopup extends PureComponent<Props> {
static defaultProps = {
onClose: () => {},
width: 500,
height: 500,
url: "",
title: ""
};
externalWindow: any;
codeCheck: any;
componentWillUnmount() {
if (this.externalWindow) {
this.externalWindow.close();
}
}
createPopup = () => {
const {url, title, width, height, onCode} = this.props;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2.5;
const windowFeatures = `toolbar=0,scrollbars=1,status=1,resizable=0,location=1,menuBar=0,width=${width},height=${height},top=${top},left=${left}`;
this.externalWindow = window.open(
url,
title,
windowFeatures
);
const storageListener = () => {
try {
if (localStorage.getItem('code')) {
onCode(localStorage.getItem('code'));
this.externalWindow.close();
window.removeEventListener('storage', storageListener);
}
} catch (e) {
window.removeEventListener('storage', storageListener);
}
}
window.addEventListener('storage', storageListener);
this.externalWindow.addEventListener('beforeunload', () => {
this.props.onClose()
}, false);
};
render() {
return (
<div onClick={this.createPopup)}>
{this.props.children}
</div>
);
}
}
app.tsx
import React, {FC} from 'react'
const onCode = async (): Promise<undefined> => {
try {
const res = await <your_fetch>
} catch (e) {
console.error(e);
} finally {
window.localStorage.removeItem('code'); //remove code from localStorage
}
}
const App: FC = () => (
<OAuthPopup
url={<your_url>}
onCode={onCode}
onClose={() => console.log('closed')}
title="<your_title>">
<button type="button">Enter</button>
</OAuthPopup>
);
export default App;
我曾经在使用 window.open/window.opener bug on ms-edge
的 oauth 登录流程中遇到问题
这个问题之前我的流程是
- 在登录按钮上单击打开弹出窗口
- 成功登录后,oauth 应用重定向到我域的页面
- 然后我使用来自 oauth 响应和父 window 的数据在弹出窗口 (window.opener.fn) 中调用父 window 的函数,然后关闭子弹出窗口 window
这个问题之后我的流程是
- 在登录按钮上单击打开弹出窗口
- 创建一个 setinterval 以防万一(window.opener 未定义)
- 成功登录后,oauth 应用重定向到我域的页面
- 检查 window.opener 是否可用然后执行上述流程中的#3 和 clearInterval
- 如果 window.opener 不可用,那么因为我在我的域页面上,我尝试设置 localstorage 并尝试从父 window 的 setInterval 函数内部读取 localstorage 然后清除 localstorage 和setInterval 并继续。
- (为了向后兼容)如果 localstorage 也不可用,则设置一个客户端 cookie,其中的数据具有较短的过期时间(5-10 秒),并尝试读取其中的 cookie (document.cookie)父 window 中的 setInterval 函数并继续。
我对如何使用弹出窗口在 React 中实现 OAuth 很感兴趣 (window.open
)。
例如我有:
mysite.com
— 这是我打开弹出窗口的地方。passport.mysite.com/oauth/authorize
— 弹出窗口。
主要问题是如何在 window.open
(弹出窗口)和 window.opener
之间创建连接(众所周知,由于跨域安全性,window.opener 为空,因此我们可以'不要再使用它了)。
⇑
window.opener
is removed whenever you navigate to a different host (for security reasons), there is no way around it. The only option should be doing the payment in a frame if it is possible. The top document needs to stay on the same host.
方案:
可能的解决方案:
- 使用
setInterval
描述 here 检查打开的 window。 使用 cross-storage(恕我直言,不值得)。
那么 2019 年推荐的最佳方法是什么?
Wrapper for React - https://github.com/Ramshackle-Jamathon/react-oauth-popup
由 Khanh TO. OAuth popup with localStorage. Based on react-oauth-popup 建议。
方案:
代码:
oauth-popup.tsx:
import React, {PureComponent, ReactChild} from 'react'
type Props = {
width: number,
height: number,
url: string,
title: string,
onClose: () => any,
onCode: (params: any) => any,
children?: ReactChild,
}
export default class OauthPopup extends PureComponent<Props> {
static defaultProps = {
onClose: () => {},
width: 500,
height: 500,
url: "",
title: ""
};
externalWindow: any;
codeCheck: any;
componentWillUnmount() {
if (this.externalWindow) {
this.externalWindow.close();
}
}
createPopup = () => {
const {url, title, width, height, onCode} = this.props;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2.5;
const windowFeatures = `toolbar=0,scrollbars=1,status=1,resizable=0,location=1,menuBar=0,width=${width},height=${height},top=${top},left=${left}`;
this.externalWindow = window.open(
url,
title,
windowFeatures
);
const storageListener = () => {
try {
if (localStorage.getItem('code')) {
onCode(localStorage.getItem('code'));
this.externalWindow.close();
window.removeEventListener('storage', storageListener);
}
} catch (e) {
window.removeEventListener('storage', storageListener);
}
}
window.addEventListener('storage', storageListener);
this.externalWindow.addEventListener('beforeunload', () => {
this.props.onClose()
}, false);
};
render() {
return (
<div onClick={this.createPopup)}>
{this.props.children}
</div>
);
}
}
app.tsx
import React, {FC} from 'react'
const onCode = async (): Promise<undefined> => {
try {
const res = await <your_fetch>
} catch (e) {
console.error(e);
} finally {
window.localStorage.removeItem('code'); //remove code from localStorage
}
}
const App: FC = () => (
<OAuthPopup
url={<your_url>}
onCode={onCode}
onClose={() => console.log('closed')}
title="<your_title>">
<button type="button">Enter</button>
</OAuthPopup>
);
export default App;
我曾经在使用 window.open/window.opener bug on ms-edge
的 oauth 登录流程中遇到问题这个问题之前我的流程是
- 在登录按钮上单击打开弹出窗口
- 成功登录后,oauth 应用重定向到我域的页面
- 然后我使用来自 oauth 响应和父 window 的数据在弹出窗口 (window.opener.fn) 中调用父 window 的函数,然后关闭子弹出窗口 window
这个问题之后我的流程是
- 在登录按钮上单击打开弹出窗口
- 创建一个 setinterval 以防万一(window.opener 未定义)
- 成功登录后,oauth 应用重定向到我域的页面
- 检查 window.opener 是否可用然后执行上述流程中的#3 和 clearInterval
- 如果 window.opener 不可用,那么因为我在我的域页面上,我尝试设置 localstorage 并尝试从父 window 的 setInterval 函数内部读取 localstorage 然后清除 localstorage 和setInterval 并继续。
- (为了向后兼容)如果 localstorage 也不可用,则设置一个客户端 cookie,其中的数据具有较短的过期时间(5-10 秒),并尝试读取其中的 cookie (document.cookie)父 window 中的 setInterval 函数并继续。