在DeviceCodeCallback中在视图展示Microsoft Graph API设备代码凭证的问题
问题描述
尝试使用设备代码凭证(Device Code Credential)调用Microsoft Graph API,但无法在DeviceCode视图中显示设备代码。点击登录按钮触发的控制器代码如下:
public async Task<IActionResult> MicrosoftLogin() { var tenantId = "my-tenant-id"; var clientId = "my-client-id"; var message = ""; var options = new DeviceCodeCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud, ClientId = clientId, TenantId = tenantId, DeviceCodeCallback = (code, cancellation) => { message = code.Message; return Task.FromResult(0); }, }; _deviceCodeCredential = new DeviceCodeCredential(options); ViewData["message"] = message; return View("DeviceCode"); }
期望在MicrosoftLogin控制器中获取设备代码并传递至视图,但由于DeviceCodeCallback的触发时机问题,无法实现。尝试为回调添加async/await也无效,只有在触发MicrosoftCallback控制器调用Graph API时,code.Message才会在控制台显示:
public async Task<IActionResult> MicrosoftCallback(string userEmail) { var scopes = new[] { "Mail.Read", "User.Read" }; graph = new GraphServiceClient(_deviceCodeCredential, scopes); var messages = await graph.Me.Messages .GetAsync(requestConfiguration => { // 配置细节 }); }
需要实现在DeviceCode视图中直接显示设备代码的需求。
解决方案
核心问题分析
DeviceCodeCredential的回调只有在实际发起令牌请求时才会被触发,仅实例化DeviceCodeCredential不会启动设备代码流。之前的代码中,只有在MicrosoftCallback里调用Graph API方法时,才会触发令牌请求,进而执行DeviceCodeCallback,导致设备代码无法提前传递到视图。
优化实现方案
通过主动触发令牌请求,并使用TaskCompletionSource等待设备代码生成,同时将令牌请求放在后台执行,避免阻塞控制器返回视图:
public async Task<IActionResult> MicrosoftLogin() { var tenantId = "my-tenant-id"; var clientId = "my-client-id"; // 提前声明需要请求的权限范围 var scopes = new[] { "Mail.Read", "User.Read" }; string deviceCodeMessage = string.Empty; // 用于等待设备代码回调执行完成 var codeReceivedTcs = new TaskCompletionSource<string>(); var options = new DeviceCodeCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud, ClientId = clientId, TenantId = tenantId, DeviceCodeCallback = (code, cancellation) => { deviceCodeMessage = code.Message; // 标记设备代码已获取 codeReceivedTcs.SetResult(deviceCodeMessage); return Task.CompletedTask; }, }; _deviceCodeCredential = new DeviceCodeCredential(options); // 后台执行令牌请求,不阻塞控制器返回视图 _ = Task.Run(async () => { try { await _deviceCodeCredential.GetTokenAsync( new TokenRequestContext(scopes), CancellationToken.None); // 可在此处添加认证完成后的逻辑,比如记录状态、跳转提示等 } catch (OperationCanceledException) { // 处理用户取消认证的情况 } catch (Exception ex) { // 处理其他异常,比如超时、权限错误等 } }); // 等待设备代码生成完成 await codeReceivedTcs.Task; ViewData["message"] = deviceCodeMessage; return View("DeviceCode"); }
方案说明
- 提前声明权限范围:
GetTokenAsync需要指定请求的权限范围,需与后续Graph API调用的范围一致。 - 使用TaskCompletionSource:确保控制器等待
DeviceCodeCallback返回设备代码后,再将代码传递到视图。 - 后台执行令牌请求:通过
Task.Run将GetTokenAsync放在后台执行,避免控制器动作阻塞,用户能立即看到显示设备代码的视图。 - 异常处理:捕获认证过程中的取消、超时等异常,避免后台任务报错影响主流程。
后续回调优化
用户完成设备代码认证后,可通过页面自动刷新、按钮点击等方式触发MicrosoftCallback动作,此时_deviceCodeCredential已持有有效令牌,可直接调用Graph API获取数据。
内容的提问来源于stack exchange,提问作者Wu Bang Zheng
相关产品推荐
相关产品推荐

