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

基于MSAL的AngularJS应用调用Graph API发邮件报错排查

OBO流程调用Graph SendMail异常排查方案

问题背景

基于MSAL的AngularJS应用获取含Mail.Send权限的AccessToken,传递至ASP.NET后端后,通过OnBehalfOfCredential初始化GraphServiceClient时触发System.Net.WebException、Microsoft.Identity.Client.MsalServiceException、Azure.Identity.AuthenticationFailedException,以下是针对性排查和修复方案:

排查与修复步骤

1. 前端Scope与Token有效性校验

  • 前端请求的Scope需指定Graph API完整路径,修正:
    const emailScopes = ["https://graph.microsoft.com/Mail.Send"];
    
  • 解析前端返回的AccessToken(可用jwt.ms工具),确认:
    • aud字段值为https://graph.microsoft.com(Token受众为Graph API)
    • scp字段包含Mail.Send(Token已获取发送邮件的委派权限)
    • exp字段未过期(Token默认有效期1小时)

2. 后端Scope配置修正

OBO属于委派权限流,不能使用客户端凭证流的.default Scope,改为具体的委派权限Scope:

var scopes = new[] { "https://graph.microsoft.com/Mail.Send" };

3. Azure AD应用注册配置检查

  • 后端应用需添加委派权限的Mail.Send,并完成管理员同意(若权限需全局授权)
  • 核对tenantId、clientId、clientSecret的正确性,注意代码中_graphTenanatId存在拼写错误(应为_graphTenantId),这会直接导致授权失败

4. 异常细节定位

捕获MsalServiceException时打印错误码和消息,这是排查核心:

catch (MsalServiceException msalEx)
{
    LogRepository.LogMessage($"MSAL错误: {msalEx.ErrorCode} - {msalEx.Message}");
    throw;
}

常见错误码对应问题:

  • invalid_grant:前端Token无效/过期/权限不足
  • invalid_client:ClientId或ClientSecret错误
  • insufficient privileges:未完成权限的管理员同意

5. 代码优化与验证

初始化GraphServiceClient后,可添加简单请求验证OBO凭证有效性:

// 验证凭证是否正常
await _graphServiceClient.Me.GetAsync();

修正后代码示例

前端代码

msalServiceFactory.getEmailToken = function () {
    const account = msalInstance.getAllAccounts()[0]; 
    const emailScopes = ["https://graph.microsoft.com/Mail.Send"];
    const silentRequest = {
        scopes: emailScopes,
        account: account
    };
    return msalInstance.acquireTokenSilent(silentRequest).then(response => {
        console.log("Token acquired silently", response.accessToken);
        return response.accessToken;
    }).catch(error => {
        console.error("Silent token acquisition failed", error);
        if (error instanceof msal.InteractionRequiredAuthError) {
            return msalInstance.acquireTokenPopup({ scopes: emailScopes }).then(response => {
                console.log("Token acquired via popup", response.accessToken);
                return response.accessToken;
            }).catch(innerError => {
                console.error("Interactive token acquisition failed", innerError);
                return null;
            });
        } else {
            return null;
        }
    });
}

return msalServiceFactory;

后端代码

private void InitGraphServiceClient(string accessToken)
{
    try
    {
        var scopes = new[] { "https://graph.microsoft.com/Mail.Send" };
        var tenantId = _graphTenantId; // 修正拼写错误
        var clientId = _graphClientId;
        var clientSecret = _graphClientSecret;

        var options = new OnBehalfOfCredentialOptions
        {
            AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
        };

        var onBehalfOfCredential = new OnBehalfOfCredential(
            tenantId, clientId, clientSecret, accessToken, options);

        _graphServiceClient = new GraphServiceClient(onBehalfOfCredential, scopes);
        
        if (_graphServiceClient == null)
        {
            throw new InvalidOperationException("Failed to initialize GraphServiceClient.");
        }
    }
    catch (MsalUiRequiredException msalEx)
    {
        LogRepository.LogMessage($"MSAL UI required error: {msalEx.ErrorCode} - {msalEx.Message}");
        throw;
    }
    catch (MsalServiceException msalEx)
    {
        LogRepository.LogMessage($"MSAL Service error: {msalEx.ErrorCode} - {msalEx.Message}");
        throw;
    }
    catch (Exception ex)
    {
        LogRepository.LogMessage($"InitGraphServiceClient error: {ex.ToString()}");
        throw;
    }
}

// 发送邮件
await _graphServiceClient.Me.SendMail.PostAsync(sendMailBody);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:06:01