通过Azure AD B2C认证获取Azure Function访问令牌异常问题
Azure AD B2C认证后获取Azure Function访问令牌失败
错误信息
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
错误触发代码行:
// Try acquiring the access token silently string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
重现步骤
- 用户通过Azure AD B2C完成认证登录流程。
- 认证成功后,应用调用
ITokenAcquisition.GetAccessTokenForUserAsync()方法尝试获取Azure Function访问令牌。 - 抛出上述错误。
已尝试操作
- 在Azure AD B2C应用注册的令牌配置中启用了Access Tokens和ID Tokens。
- 已为Azure AD B2C应用授予Azure Function API的访问权限,使用作用域为
api://{azure-function-client-id}/.default。 - 尝试捕获
MsalUiRequiredException并触发重新认证流程,问题仍未解决。
预期行为
用户认证后,应用可静默获取Azure Function访问令牌;若无法静默获取,则自动触发重新认证。
问题代码
option.Events.OnTokenValidated = async context => { var claims = context.Principal.Claims; var signClaimUser = new SignClaimModel { email = claims.FirstOrDefault(c => c.Type == "emails" || c.Type == "email")?.Value, givenName = claims.FirstOrDefault(c => c.Type == "given_name")?.Value, surname = claims.FirstOrDefault(c => c.Type == "family_name")?.Value, objectId = claims.FirstOrDefault(c => c.Type == "oid")?.Value, userPrincipalName = claims.FirstOrDefault(c => c.Type == "upn")?.Value, tenantId = claims.FirstOrDefault(c => c.Type == "tid")?.Value }; // Serialize the model var json = JsonConvert.SerializeObject(signClaimUser); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Prepare HTTP client to call the Azure Function using var httpClient = new HttpClient(); // Get the function URL and key from your appsettings.json var functionBaseUrl = builder.Configuration["AzureFunction:FunctionUrl"]?.TrimEnd('/'); var functionKey = builder.Configuration["AzureFunction:FunctionKey"]; var functionUrl = $"{functionBaseUrl}?code={functionKey}"; // Retrieve ITokenAcquisition from the DI container to acquire a token var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); // The scope for the Azure Function (make sure your Azure Function API client ID is correctly set in appsettings.json) var azureFunctionApiClientId = builder.Configuration["AzureAdB2CAzureFunctionAPI:ClientId"]; string[] scopes = new[] { $"api://{azureFunctionApiClientId}/.default" }; try { // Try acquiring the access token silently string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); // If we get a valid access token, add it to the authorization header for the request httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // Call the Azure Function var response = await httpClient.PostAsync(functionUrl, content); // Optionally, handle the response (e.g., logging, error handling, etc.) if (!response.IsSuccessStatusCode) { var errorMessage = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Error calling Azure Function: {errorMessage}"); } } catch (MsalUiRequiredException) { // This exception occurs if the user needs to reauthenticate and provide consent // Trigger the user sign-in process interactively var redirectUrl = context.HttpContext.Request.Scheme + "://" + context.HttpContext.Request.Host.Value + "/signin-oidc"; // Your redirect URI // Redirect the user to the sign-in page context.Response.Redirect(redirectUrl); } catch (Exception ex) { Console.WriteLine($"Error acquiring token or calling Azure Function: {ex.Message}"); } };
解决方案
核心原因
OnTokenValidated是令牌验证完成后的早期事件,此时MSAL尚未将用户会话持久化到缓存中,ITokenAcquisition无法定位到对应的用户账户,因此抛出MsalUiRequiredException。同时,该事件中直接执行重定向可能因响应生命周期逻辑冲突而失效。
修复方案
方案1:将API调用移至认证完成后的请求流程
避免在OnTokenValidated中直接处理令牌获取和API调用,改为存储用户信息到上下文,后续在Controller Action中执行:
// 在OnTokenValidated中仅存储用户信息 option.Events.OnTokenValidated = context => { var signClaimUser = new SignClaimModel { email = context.Principal.Claims.FirstOrDefault(c => c.Type is "emails" or "email")?.Value, givenName = context.Principal.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value, surname = context.Principal.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value, objectId = context.Principal.Claims.FirstOrDefault(c => c.Type == "oid")?.Value, tenantId = context.Principal.Claims.FirstOrDefault(c => c.Type == "tid")?.Value }; context.HttpContext.Items["SignClaimUser"] = signClaimUser; return Task.CompletedTask; };
然后在Controller中处理:
public async Task<IActionResult> PostAuthRedirect() { var signClaimUser = HttpContext.Items["SignClaimUser"] as SignClaimModel; if (signClaimUser == null) return RedirectToAction("Index"); var tokenAcquisition = HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); var clientId = Configuration["AzureAdB2CAzureFunctionAPI:ClientId"]; var scopes = new[] { $"api://{clientId}/.default" }; try { var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var functionUrl = $"{Configuration["AzureFunction:FunctionUrl"].TrimEnd('/')}?code={Configuration["AzureFunction:FunctionKey"]}"; var content = new StringContent(JsonConvert.SerializeObject(signClaimUser), Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(functionUrl, content); if (!response.IsSuccessStatusCode) { var errorMsg = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Function call failed: {errorMsg}"); } } catch (MsalUiRequiredException) { return Challenge(new AuthenticationProperties { RedirectUri = "/PostAuthRedirect" }); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } return RedirectToAction("Home"); }
方案2:手动指定账户获取令牌(仅当必须在OnTokenValidated中执行时)
从当前ClaimsPrincipal中提取账户信息,手动传入AcquireTokenSilent:
try { var account = await tokenAcquisition.GetAccountAsync(context.Principal); if (account == null) { context.HandleResponse(); context.Response.Redirect("/signin-oidc"); return; } var tokenResult = await tokenAcquisition.AcquireTokenSilent(scopes, account).ExecuteAsync(); string accessToken = tokenResult.AccessToken; // 后续调用Azure Function逻辑... } catch (MsalUiRequiredException) { context.HandleResponse(); context.Response.Redirect($"{context.HttpContext.Request.Scheme}://{context.HttpContext.Request.Host}/signin-oidc"); }
关键注意点
- 在
OnTokenValidated中执行重定向时,必须先调用context.HandleResponse()终止当前响应处理,否则重定向指令可能被忽略。 - 确保应用配置中已正确设置Azure AD B2C的租户ID、客户端ID、回调URI等参数,且Azure Function API的权限已被正确授予并同意。
内容的提问来源于stack exchange,提问作者Ret 2
相关产品推荐
相关产品推荐

