Issue with Azure AD B2C Custom Policy: "Basic credentials specified for 'REST-fetchUserProfile-APAC' are invalid
Description
I am working on an Azure AD B2C custom policy for the Global Identity Framework, designed to support traveling user sign-ins across multiple regions. This setup involves two major components Right now im only working on region tenant artifact, but this traveling user sign in case applies and has the same logic to both meaning the technical profiles used for both artifacts would be the same:
Funnel Tenant: Handles the initial user sign-in and routes the request based on the user's region.
Regional Tenant: Processes sign-ins for users registered in specific regions.
For users in the EMEA tenant, the sign-in process works as expected. However, when users registered in the APAC tenant attempt to sign in, the policy correctly identifies their region and routes the request to the REST-login-NonInteractive-APAC
and REST-fetchUserProfile-APAC
technical profiles. Unfortunately, it fails with the error: "Basic credentials specified for 'REST-fetchUserProfile-APAC' are invalid. Check that the credentials are correct and that access has been granted by the resource."
Custom Policy - Self-Asserted Local Account Sign-In Profile The first step in the user journey always begins with the SelfAsserted-LocalAccountSignin-Email profile, which includes the following configuration:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignin</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<Item Key="setting.forgotPasswordLinkOverride">ForgotPasswordExchange</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- <ValidationTechnicalProfile ReferenceId="REST-getTokenforExternalApiCalls" /> -->
<ValidationTechnicalProfile ReferenceId="REST-regionLookup" />
<ValidationTechnicalProfile ReferenceId="login-NonInteractive">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>region</Value>
<Value>EMEA</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-login-NonInteractive-APAC">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>region</Value>
<Value>APAC</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-fetchUserProfile-APAC">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>region</Value>
<Value>APAC</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Below are the two key technical profiles for APAC users. Microsoft has provided these 2 profiles in their proof of concept documentations and i am following them exactly how they are listed. https://learn.microsoft.com/en-us/azure/active-directory-b2c/b2c-global-identity-proof-of-concept-funnel:
REST-login-NonInteractive-APAC: Responsible for authenticating the user in the APAC tenant via ROPC.
<TechnicalProfile Id="REST-login-NonInteractive-APAC">
<DisplayName>non interactive authentication to APAC</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://login.microsoftonline.com/zappsecincAdB2Capac.onmicrosoft.com/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Form</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="apac_client_id" PartnerClaimType="client_id" DefaultValue="fb40528b-8db9-42f3-90a2-b119f00d9645" />
<InputClaim ClaimTypeReferenceId="ropc_grant_type" PartnerClaimType="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" />
<InputClaim ClaimTypeReferenceId="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://graph.microsoft.com/.default" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="graph_bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
REST-fetchUserProfile-APAC: Fetches user profile details using a Graph API call.
<TechnicalProfile Id="REST-fetchUserProfile-APAC">
<DisplayName>fetch user profile cross tenant</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://graph.microsoft.com/beta/me</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="UseClaimAsBearerToken">graph_bearerToken</Item>
<Item Key="SendClaimsIn">Url</Item>
<Item Key="DebugMode">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="graph_bearerToken" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="id" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Issue Details When an APAC user attempts to sign in:
The policy correctly identifies the user's region as "APAC" using the REST-regionLookup technical profile. The REST-login-NonInteractive-APAC profile successfully generates an access token (graph_bearerToken) using the ROPC flow. However, when the REST-fetchUserProfile-APAC technical profile attempts to use the graph_bearerToken to call the Graph API, it fails with the error: "Basic credentials specified for 'REST-fetchUserProfile-APAC' are invalid. Check that the credentials are correct and that access has been granted by the resource."
one thing additionally (not sure if this helps) is that i ran a curl command to obtain the access token so i can decode it in jwt.io. this is the test user in my apac tenant that i am attempting to login with in the emea tenant. here is the curl command:
curl -X POST "https://login.microsoftonline.com/zappsecincAdB2Capac.onmicrosoft.com/oauth2/v2.0/token"-H "Content-Type: application/x-www-form-urlencoded"-d "client_id=<b2cappidinapactenanthere>"-d "grant_type=password"-d "username=<myusernamehere>" -d "password=<passwordhere>"-d "scope=openid" -d "nca=1"
this generates an access token, for which when decoding in jwt.io i get this. im not sure if its an issue but i dont see user.readwrite.all or directory.readwrite.all in the scp claim:
"scp": "openid profile email",
Steps Taken Verified the App Registration:
API Permissions: Added User.ReadWrite.All and Directory.ReadWrite.All permissions to the app. Admin Consent: Granted admin consent for all permissions. Authentication Settings: Enabled public client flows (for ROPC). Verified Input Claims:
The scope is set to https://graph.microsoft.com/.default in both REST-login-NonInteractive-APAC and REST-fetchUserProfile-APAC. Debugged via Application Insights:
Confirmed that the REST-login-NonInteractive-APAC profile successfully returns an access token. The error occurs when the REST-fetchUserProfile-APAC profile uses the token to fetch user details. Reviewed Microsoft Documentation:
Followed the recommended structure for global identity framework policies, including ROPC authentication and cross-tenant Graph API calls.
Questions Are there specific additional configurations needed in the APAC tenant's app registration to allow the REST-fetchUserProfile-APAC profile to successfully call the Graph API using the token? Could the issue be related to the way the graph_bearerToken is being passed as a bearer token in REST-fetchUserProfile-APAC? Are there other ways to debug or trace the root cause of the "Basic credentials invalid" error in this scenario?
relevant logs from app insights:
{ "Key": "ValidationTechnicalProfile", "Value": { "Values": [ { "Key": "TechnicalProfileId", "Value": "REST-regionLookup" }, { "Key": "MappingPartnerTypeForClaim", "Value": { "PartnerClaimType": "signInName", "PolicyClaimType": "signInName" } } ] } }, { "Key": "Precondition", "Value": { "$id": "1", "Type": 1, "ExecuteActionsIf": false, "ActionTypes": [ 1 ], "Values": [ "region", "EMEA" ] } }, { "Key": "SkippingStep", "Value": "login-NonInteractive" }, { "Key": "Precondition", "Value": { "$id": "2", "Type": 1, "ExecuteActionsIf": false, "ActionTypes": [ 1 ], "Values": [ "region", "APAC" ] } }, { "Key": "TechnicalProfileEnabled", "Value": { "EnabledRule": "Always", "EnabledResult": true, "TechnicalProfile": "REST-login-NonInteractive-APAC" } }, { "Key": "ValidationTechnicalProfile", "Value": { "Values": [ { "Key": "TechnicalProfileId", "Value": "REST-login-NonInteractive-APAC" }, { "Key": "MappingDefaultValueForClaim", "Value": { "PartnerClaimType": "client_id", "PolicyClaimType": "apac_client_id" } }, { "Key": "MappingDefaultValueForClaim", "Value": { "PartnerClaimType": "grant_type", "PolicyClaimType": "ropc_grant_type" } }, { "Key": "MappingPartnerTypeForClaim", "Value": { "PartnerClaimType": "username", "PolicyClaimType": "signInName" } }, { "Key": "MappingPartnerTypeForClaim", "Value": { "PartnerClaimType": "password", "PolicyClaimType": "password" } }, { "Key": "MappingDefaultValueForClaim", "Value": { "PartnerClaimType": "scope", "PolicyClaimType": "scope" } }, { "Key": "MappingDefaultValueForClaim", "Value": { "PartnerClaimType": "nca", "PolicyClaimType": "nca" } } ] } }, { "Key": "Precondition", "Value": { "$id": "3", "Type": 1, "ExecuteActionsIf": false, "ActionTypes": [ 1 ], "Values": [ "region", "APAC" ] } }, { "Key": "TechnicalProfileEnabled", "Value": { "EnabledRule": "Always", "EnabledResult": true, "TechnicalProfile": "REST-fetchUserProfile-APAC" } }, { "Key": "ValidationTechnicalProfile", "Value": { "Values": [ { "Key": "TechnicalProfileId", "Value": "REST-fetchUserProfile-APAC" }, { "Key": "MappingPartnerTypeForClaim", "Value": { "PartnerClaimType": "graph_bearerToken", "PolicyClaimType": "graph_bearerToken" } } ] } }, { "Key": "Exception", "Value": { "Kind": "Handled", "HResult": "80131500", "Message": "ErrorCodes: AADB2C90027", "Data": { "IsPolicySpecificError": false } } } ] } } ] },
I would appreciate any insights or recommendations to resolve this issue. Thank you in advance!