繁体   English   中英

OAuth2 Discord授权弹窗流程

[英]OAuth2 Discord authorization popup flow

我正在为我的 discord 机器人开发一个管理面板,我目前正在实施 OAuth2 授权。

我的前端和后端托管在两个独立的服务器上,我不知道如何处理发送/存储访问令牌。 我当前的工作流程依赖于将 discord 授权重定向到后端(我不希望它重定向到前端,因为它会涉及使用客户端代码对后端进行额外调用以创建令牌),并且我能够使用收到的客户端代码调用 discord API 并获取访问令牌,但我不知道如何正确存储它。

当前流量:

  1. 在浏览器 (localhost:3000) 中,我单击“登录”按钮,打开带有 discord 授权站点的弹出窗口。
const LINK = "https://discord.com/api/oauth2/authorize?client_id=somecode&redirect_uri=http%3A%2F%2F127.0.0.1%3A3010%2Fapi%2Fauth&response_type=code&scope=identify%20guilds";

function onLinkClicked(event: Event) {
    event.preventDefault();

    const popup = window.open(LINK, 'popup', 'width=600,height=800');
}
  1. 授权后我重定向到服务器 url (localhost:3010/api/auth?code=somecode)。
  2. 我在那里打电话给 discord API 以接收访问令牌。
MainRouter.get('/auth', async (req, res) => {
    const code = req.query.code;
    const params: any = {
        'client_id': process.env.APP_ID,
        'client_secret': process.env.APP_SECRET,
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': 'http://127.0.0.1:3010/api/auth'
    };

    try {
        const tokenResponse = await axios.post(`${API_ENDPOINT}/oauth2/token`, new URLSearchParams(params));
        req.session.token = tokenResponse.data.access_token; // when I make another call from the frontend, it doesn't see the token stored in the session here, because I'm saving it in the wrong place, sessionID here doesn't match my sessionID when I make calls from the frontend itself.
        res.send('OK');
    } catch(error) {
        console.error(error);
    }
});

现在我被卡住了,因为我无法将令牌存储在会话/cookie 中,而且我也不知道如何将其存储在数据库中。

我不希望 discord 授权重定向到前端站点,在那里我将使用客户端代码再次调用后端,然后将其转换为访问令牌并存储它,因为它看起来很乱,我希望可以有另一个解决方案。

我看到 probot.io 网站,当它创建授权弹出窗口时,它会将您重定向到 api.probot.io?code=somecode,这是一个不同的网站,立即关闭并让您登录到仪表板,我不知道如何达到类似的效果。

所以,我的问题是,当我不从前端调用时,有没有办法将访问令牌存储在前端 session 中? 既然答案可能是否定的,那么如何改进这个工作流程呢?

我想出了一个看起来不错的解决方案,但我愿意接受建议。

在重定向到后端的 /api/auth 后,我没有立即获取访问令牌,而是将消息从弹出窗口发送到包含客户端代码的主客户端。

MainRouter.get('/auth', async (req, res) => {
    const code = req.query.code;
    
    res.set('Content-Type', 'text/html');
    res.send(Buffer.from(`<script>window.opener.postMessage("${code}", "http://localhost:3000");window.close();</script>`));
});

在客户端收到代码后,我又进行了一次 /api/auth 调用,但这次是 POST,它希望将代码作为参数。

window.addEventListener('message', async (event) => {
    if(event.origin == 'http://127.0.0.1:3010') {
        console.log(event.data);
        const res = await callAPI('/auth', { code: event.data }, 'post');
        console.log(res);
    }
});

然后后端会将其交换为访问令牌并将其存储在 session 中,这次可以完美运行。

MainRouter.post('/auth', async (req, res) => {
    const code = req.body.code;
    const params: any = {
        'client_id': process.env.APP_ID,
        'client_secret': process.env.APP_SECRET,
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': 'http://127.0.0.1:3010/api/auth'
    };

    try {
        const tokenResponse = await axios.post(`${API_ENDPOINT}/oauth2/token`, new URLSearchParams(params));
        console.log(tokenResponse.data.access_token);

        req.session.token = tokenResponse.data.access_token; // this will work now, since I'm calling from the frontend side
        res.send({ code: 200 });
    } catch(error) {
        res.send({ code: 301, msg: error });
    }
});

在意识到我可以将消息从弹出窗口发送到主窗口后,我想到了这个,但我不知道它是否是最佳和安全的解决方案。 我在想是否存在解决问题的“更清洁”的解决方案......

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM