You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 16集成Discord OAuth2遇跨域问题:无法获取授权code

问题:Angular 16中Discord OAuth2授权无法提取code(跨域限制)

场景描述

我需要在Angular 16应用中实现Discord账号认证,采用Discord的OAuth2协议:通过window.open打开Discord授权UI,用户授权后跳转到指定redirect URL,URL中附带用于获取access_token的code。但目前无法读取授权后窗口的URL,触发跨域错误。

错误信息

Failed to read a named property 'hash' from 'Location': Blocked a frame with origin "http://localhost:4200" from accessing a cross-origin frame.

当前代码实现

private openWinDiscord = (clientId: string, redirect: string) => new Promise<string>((resolve, reject) => {
        const scopes = 'identify+email';    // scopes seperated by +
        const redirect2 = 'my_redirect_URL';    // Todo: delete this and put redirect in the calling method

        try {
            this.loading = true;
            const endPoint = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirect2}&scope=${scopes}`; // todo: add '&prompt=none' at the end
            const strWindowFeatures = 'location=yes,height=620,width=520,scrollbars=no,resizable=no,status=yes';
            const discordWindow = window.open(endPoint,
                'discordLoginNav',
                strWindowFeatures
            );

            const closeWindow = () => {
                this.loading = false;
                discordWindow.close();
            };

            const discordIntervalHash = setInterval(() => {
                try {
                    const response_code = discordWindow['location'].hash;
                    if (response_code) {
                        const code = response_code.match(/\#(?:code)\=([\S\s]*?)\&/)[1];
                        if (code) {
                            clearInterval(discordIntervalHash);
                            closeWindow();
                            resolve(code);
                        }

                    } else {
                        if (discordWindow['location']['href'].includes('error')) {
                            clearInterval(discordIntervalHash);
                            closeWindow();
                            reject({
                                message: 'Connection error'
                            });
                        }
                    }
                } catch (e) {
                    console.log('Error parsing query and extracting token', e);
                    this.loading = false;
                    return null;
                }
            }, 1200);

        } catch (e) {
            console.log('Error', e);
            this.loading = false;
            reject('Error');
        }
    })

解决方案

首先明确:跨域限制是浏览器的核心安全机制,无法绕过,你需要换一种方式实现授权流程,以下是两种可行方案:

方案1:使用Angular路由作为回调地址,通过消息传递获取code

这是前端场景下最常用的方案,核心思路是让授权后的跳转落在自己的应用路由内,避免跨域访问限制:

  1. 配置回调地址:在Discord开发者后台,将redirect URI设置为Angular应用内的一个路由,比如http://localhost:4200/discord-callback,必须保证和代码中使用的地址完全一致(包括协议、域名、端口)。
  2. 创建回调组件:在Angular中新建DiscordCallbackComponent,负责提取URL中的code并通知主窗口。
  3. 修改授权窗口逻辑:
    • 不再轮询授权窗口的location,而是监听主窗口的message事件,等待回调组件传递code。
    • 注意:Discord OAuth2的response_type=code模式下,code是放在URL的query参数里,不是hash中,你之前的代码错误读取了hash,这也是问题之一。

关键代码示例

  • 主窗口的授权方法:
private openWinDiscord(clientId: string): Promise<string> {
  return new Promise((resolve, reject) => {
    this.loading = true;
    const scopes = 'identify+email';
    const redirectUri = encodeURIComponent('http://localhost:4200/discord-callback');
    const endPoint = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scopes}`;
    const discordWindow = window.open(endPoint, 'discordLoginNav', 'location=yes,height=620,width=520,scrollbars=no,resizable=no,status=yes');

    // 监听message事件接收code
    const messageListener = (event: MessageEvent) => {
      // 验证消息来源,防止恶意注入
      if (event.origin !== window.location.origin) return;
      
      if (event.data.type === 'DISCORD_CODE') {
        window.removeEventListener('message', messageListener);
        this.loading = false;
        discordWindow.close();
        resolve(event.data.code);
      } else if (event.data.type === 'DISCORD_ERROR') {
        window.removeEventListener('message', messageListener);
        this.loading = false;
        discordWindow.close();
        reject(event.data.error);
      }
    };
    window.addEventListener('message', messageListener);

    // 监听窗口关闭,处理用户主动取消授权的情况
    const closeCheckInterval = setInterval(() => {
      if (discordWindow.closed) {
        window.removeEventListener('message', messageListener);
        clearInterval(closeCheckInterval);
        this.loading = false;
        reject(new Error('用户取消授权'));
      }
    }, 500);
  });
}
  • DiscordCallbackComponent的初始化逻辑:
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit } from '@angular/core';

@Component({
  template: '<p>正在处理授权,请稍候...</p>'
})
export class DiscordCallbackComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.queryParams.subscribe(params => {
      const code = params['code'];
      const error = params['error'];

      // 向主窗口发送消息
      if (code) {
        window.opener.postMessage({ type: 'DISCORD_CODE', code }, window.opener.origin);
      } else if (error) {
        window.opener.postMessage({ type: 'DISCORD_ERROR', error }, window.opener.origin);
      } else {
        window.opener.postMessage({ type: 'DISCORD_ERROR', error: '授权失败' }, window.opener.origin);
      }
      // 关闭回调窗口
      window.close();
    });
  }
}
  • 在Angular路由中注册回调组件:
const routes: Routes = [
  // ...其他路由配置
  { path: 'discord-callback', component: DiscordCallbackComponent }
];

方案2:使用后端作为中间层处理授权流程

如果对安全性要求较高(不想让access_token暴露在前端),可以让后端完成OAuth2的授权码交换:

  1. 将Discord的redirect URI设置为你的后端接口,比如https://your-backend.com/discord/callback。
  2. 前端打开Discord授权窗口时,redirect URI指向该后端接口。
  3. 后端接收到code后,直接调用Discord的token接口获取access_token和用户信息,然后生成会话或返回用户标识给前端。
  4. 后端处理完成后,跳转到前端的指定页面,携带授权状态或用户信息。

这种方式完全规避了前端跨域问题,同时更安全,适合需要严格权限控制的场景。


内容的提问来源于stack exchange,提问作者Franco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:26:09