向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:若当前应用为你创建并拥有,该权限足够(权限范围更小,更安全)
配置步骤:
- 登录Azure门户,进入你的应用注册页面
- 切换到「API权限」栏目,点击「添加权限」
- 选择「Microsoft Graph」→「应用权限」
- 搜索并勾选上述权限之一,点击「添加权限」
- 点击「授予管理员同意」(必须完成此步骤,权限才会生效)
代码修正建议
- 将同步调用替换为
async/await,避免ASP.NET页面死锁 - 用强类型对象序列化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
相关产品推荐
相关产品推荐

