MSAL On-Behalf-Of(OBO)流程令牌获取失败求助
问题描述
尝试通过MSAL的On-Behalf-Of(OBO)流程为用户获取令牌时,出现JSON解析错误:响应以'<'开头,无法解析为JSON内容。相关代码及配置如下:
获取令牌代码
[Authorize] [RequiredScope(scopeRequiredByAPI)] [ApiController] [Route("[controller]")] public class UserDetailController : ControllerBase { const string scopeRequiredByAPI = "access_as_user"; private readonly IUserService _userService; private readonly ILogger<UserDetailController> _logger; private readonly ITokenAcquisition _tokenAcquisition; private readonly AzureAdB2COpenIdConnectOptions _settings; public UserDetailController(ILogger<UserDetailController> logger, IUserService userService, ITokenAcquisition tokenAcquisition, IOptions<AzureAdB2COpenIdConnectOptions> settings) { _logger = logger; _userService = userService; _tokenAcquisition = tokenAcquisition; _settings = settings.Value; } [HttpGet] public async Task<User> GetAsync() { User currentUser = new User() { DisplayName = "Son" }; //return currentUser; try { var scopes = new string[] { "https://graph.microsoft.com/.default", //"User.ReadWrite.All", }; var user = HttpContext?.User; var result = await _tokenAcquisition.GetAuthenticationResultForUserAsync(scopes, _settings.TenantId, _settings.SignUpSignInPolicyId, user);
Web应用配置
builder.Services .AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, Constants.AzureAdB2C) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(builder.Configuration.GetSection("GraphApi")) .AddInMemoryTokenCaches()
Web API配置
builder.Services .AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, Constants.AzureAdB2C, Constants.AzureAdB2C) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph(options => { options.BaseUrl = GraphApiOption?.EndPoint ?? "https://graph.microsoft.com/v1.0"; options.Scopes = GraphApiOption?.Scopes; }) .AddInMemoryTokenCaches();
错误日志
info: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] === Token Acquisition (OnBehalfOfRequest) started: Authority: https://hellioshop.b2clogin.com/tfp/hellioshop.onmicrosoft.com/b2c_1_sign_in_gk/ Scope: https://graph.microsoft.com/.default ClientId: 7cc33dc4-5218-4798-a1d3-f79a782a2824 warn: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] Only in-memory caching is used. The cache is not persisted and will be lost if the machine is restarted. It also does not scale for a web app or web API, where the number of users can grow large. In production, web apps and web APIs should use distributed caching like Redis. See https://aka.ms/msal-net-cca-token-cache-serialization info: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] [Instance Discovery] Skipping Instance discovery for non-AAD authority. info: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] [Internal cache] Total number of cache partitions found while getting access tokens: 0 info: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] [FindAccessTokenAsync] Discovered 0 access tokens in cache using partition key: Ey02OURgEZIIpcqKfoU7GyOswE8Xm_5eOtv-sJBftlU info: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:08Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] [OBO request] Fetching tokens via normal OBO flow. info: System.Net.Http.HttpClient.Default.LogicalHandler[100] Start processing HTTP request POST https://hellioshop.b2clogin.com/tfp/hellioshop.onmicrosoft.com/b2c_1_sign_in_gk/oauth2/v2.0/token info: System.Net.Http.HttpClient.Default.ClientHandler[100] Sending HTTP request POST https://hellioshop.b2clogin.com/tfp/hellioshop.onmicrosoft.com/b2c_1_sign_in_gk/oauth2/v2.0/token info: System.Net.Http.HttpClient.Default.ClientHandler[101] Received HTTP response headers after 567.4847ms - 200 info: System.Net.Http.HttpClient.Default.LogicalHandler[101] End processing HTTP request after 582.3227ms - 200 fail: Microsoft.Identity.Web.TokenAcquisition[0] True MSAL 4.55.0.0 MSAL.NetCore .NET 7.0.9 Microsoft Windows 10.0.19045 [2023-08-13 18:22:09Z - d9208054-ea81-44cc-bf49-7ea6a2c1d874] System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0. ---> System.Text.Json.JsonReaderException: '<' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0. at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes) at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker) at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first) at System.Text.Json.Utf8JsonReader.ReadSingleSegment() at System.Text.Json.Utf8JsonReader.Read() at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) --- End of inner exception stack trace --- at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.Serialization.JsonConverter`1.ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo) at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerContext context) at Microsoft.Identity.Client.Utils.JsonHelper.DeserializeFromJson[T](String json) at Microsoft.Identity.Client.OAuth2.OAuth2Client.CreateResponse[T](HttpResponse response, RequestContext requestContext) at Microsoft.Identity.Client.OAuth2.OAuth2Client.ExecuteRequestAsync[T](Uri endPoint, HttpMethod method, RequestContext requestContext, Boolean expectErrorsOn200OK, Boolean addCommonHeaders, Func`2 onBeforePostRequestData) at Microsoft.Identity.Client.OAuth2.OAuth2Client.GetTokenAsync(Uri endPoint, RequestContext requestContext, Boolean addCommonHeaders, Func`2 onBeforePostRequestHandler) at Microsoft.Identity.Client.OAuth2.TokenClient.SendHttpAndClearTelemetryAsync(String tokenEndpoint, ILoggerAdapter logger) at Microsoft.Identity.Client.OAuth2.TokenClient.SendTokenRequestAsync(IDictionary`2 additionalBodyParameters, String scopeOverride, String tokenEndpointOverride, CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.SendTokenRequestAsync(IDictionary`2 additionalBodyParameters, CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.FetchNewAccessTokenAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.RefreshRtOrFetchNewAccessTokenAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
解决建议
响应以'<'开头说明返回的是HTML错误页面而非预期的JSON令牌响应,按以下步骤排查修复:
- 修正Token端点与Authority:B2C的OBO流程不能使用用户流(user flow)的端点,需改用租户级Token端点,格式为
https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/oauth2/v2.0/token,去掉路径中的tfp/<user-flow-id>部分。 - 调整权限范围:OBO是委派权限流程,避免使用
https://graph.microsoft.com/.default(该范围适用于应用权限),改用具体的委派权限,比如https://graph.microsoft.com/User.Read。 - 检查应用注册配置:确认Web API的Azure AD B2C应用注册中,已添加所需的Microsoft Graph委派权限并完成管理员同意;同时确保Web API被允许代表用户发起OBO请求。
- 验证传入令牌有效性:用JWT解码工具检查Web API收到的用户令牌,确认
aud(受众)、iss(颁发者)声明正确,且令牌未过期。 - 捕获实际响应内容:添加日志输出Token端点返回的完整HTML内容,明确具体错误原因(如权限不足、配置错误)。
内容的提问来源于stack exchange,提问作者Sơn Lưu Thuần
相关产品推荐
相关产品推荐

