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

向Microsoft Authenticator发通知的Azure Graph API权限及405错误排查

解决向Microsoft Authenticator发送推送通知的405错误及权限配置问题

核心问题分析

405 Method Not Allowed错误主要有两个触发点:

  • API端点路径或调用方式有误
  • 应用缺少调用该API的必要权限

结合你的场景,API端点路径是正确的,但权限配置缺失是核心问题,同时代码中的同步调用方式存在潜在死锁风险。

所需Microsoft Graph权限配置

由于你使用的是客户端凭证流(client_credentials),需要为Azure应用注册添加应用权限,可选以下两种:

  • Application.ReadWrite.All:允许应用读写所有应用注册信息,包含发送自定义通知的权限
  • Application.ReadWrite.OwnedBy:若当前应用为你创建并拥有,该权限足够(权限范围更小,更安全)

配置步骤:

  1. 登录Azure门户,进入你的应用注册页面
  2. 切换到「API权限」栏目,点击「添加权限」
  3. 选择「Microsoft Graph」→「应用权限」
  4. 搜索并勾选上述权限之一,点击「添加权限」
  5. 点击「授予管理员同意」(必须完成此步骤,权限才会生效)

代码修正建议

  1. 将同步调用替换为async/await,避免ASP.NET页面死锁
  2. 用强类型对象序列化JSON,减少格式错误

修正后的完整代码:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI;
using Newtonsoft.Json;

namespace YourNamespace
{
    public partial class PushNotification : Page
    {
        private readonly string _tenantId = "YOUR_TENANT_ID";
        private readonly string _clientId = "YOUR_CLIENT_ID";
        private readonly string _clientSecret = "YOUR_CLIENT_SECRET";

        protected async void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                string accessToken = await GetAccessTokenAsync(_tenantId, _clientId, _clientSecret);

                if (!string.IsNullOrEmpty(accessToken))
                {
                    string graphApiEndpoint = $"https://graph.microsoft.com/v1.0/applications/{_clientId}/sendCustomNotificationToDevices";

                    using (var client = new HttpClient())
                    {
                        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                        var notificationPayload = new
                        {
                            messageTemplateName = "myMessage",
                            notification = new
                            {
                                type = "basic",
                                title = "Hello",
                                body = "This is a test notification from your app."
                            }
                        };

                        string notificationJson = JsonConvert.SerializeObject(notificationPayload);
                        var content = new StringContent(notificationJson, Encoding.UTF8, "application/json");
                        var response = await client.PostAsync(graphApiEndpoint, content);

                        if (response.IsSuccessStatusCode)
                        {
                            ResultLabel.Text = "Notification sent successfully!";
                        }
                        else
                        {
                            string errorDetails = await response.Content.ReadAsStringAsync();
                            ResultLabel.Text = $"Failed to send notification: {response.ReasonPhrase}\nDetails: {errorDetails}";
                        }
                    }
                }
                else
                {
                    ResultLabel.Text = "Failed to obtain access token";
                }
            }
        }

        private async Task<string> GetAccessTokenAsync(string tenantId, string clientId, string clientSecret)
        {
            string tokenUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";

            using (var client = new HttpClient())
            {
                var requestParams = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("grant_type", "client_credentials"),
                    new KeyValuePair<string, string>("client_id", clientId),
                    new KeyValuePair<string, string>("client_secret", clientSecret),
                    new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"),
                });

                var response = await client.PostAsync(tokenUrl, requestParams);

                if (response.IsSuccessStatusCode)
                {
                    var jsonResult = await response.Content.ReadAsStringAsync();
                    dynamic json = JsonConvert.DeserializeObject(jsonResult);
                    return json.access_token;
                }

                return null;
            }
        }
    }
}

额外验证步骤

  • 验证访问令牌权限:用JWT解析工具解码token,检查roles字段是否包含你配置的权限(如Application.ReadWrite.All)
  • 确认设备关联:只有已注册到该应用的设备才能接收推送通知

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:28:23