how to reset MFA and get a new QR when using azure b2c, MFA with REST API - azure

i've followed https://github.com/azure-ad-b2c/samples/tree/master/policies/custom-mfa-totp to build MFA in azure B2C and it's working fine. just one question, after user scanned the QR code and register their phone, if they want to use another phone or reset their MFA, how can they get the QR code agian?

Any type of account management should existing on a 'Profile Edit' policy. This sample does not include it. You would need to pull the components out of the TrustFrameworkExtensions.xml like the AppFactor-Register, AppFactor-Challenge, ect., technical profile and add to your new user journey for ProfileEdit - potentially created a whole new experience. Of course, adding a write technical profile back into the directory will be needed to 'update' any information that is stored.
Most of the components to achieve your scenario is already there with this sample - just create a new user journey called "ProfileEditwithQR" or something and copy the starter pack profileEdit user journey to your Extension file (if you haven't already done this) - then add the steps from this sample to it.
This will require you to have a minimum of 300 level knowledge on custom policies to put this together.

I created a policy which allows to re-create the QR code.
Prerequisites: You have been able to run this sample in you tenant.
Add this to the TrustFrameworkExtension.xml:
<UserJourney Id="EditQR" DefaultCpimIssuerTechnicalProfileReferenceId="JwtIssuer">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is alwasy executed.
It generates a verification app secret key for the user (none interactive step). -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AppFactorGenerateTotpWebHook" TechnicalProfileReferenceId="AppFactor-GenerateTotpWebHook" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is alwasy executed.
It registers a verification app through QR code that mobile authentication app should scan. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AppFactorRegisterExchange" TechnicalProfileReferenceId="AppFactor-Register" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is always executed.
It updates the verification app time step matched for a given user in the Azure Active Directory.
An error is raised if the user does not exist. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADWriteUserAppCodeByObjectId" TechnicalProfileReferenceId="AAD-WriteUserAppCodeByObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
and this is the new user flow, I called it EditQR.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="yourtenant.onmicrosoft.com"
PolicyId="B2C_1A_editQr"
PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_editQr">
<BasePolicy>
<TenantId>yourtenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="EditQR" />
<UserJourneyBehaviors>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
Test it using http://jwt.ms, you will be promted to login and then redirected to the QR page.
Cheers!

Related

Azure B2C Prevent user entries

I published an application on my Azure B2C tenant. There are two auth methods, one federated SAML provider and the regular Azure AD tenant. I want to avoid the users from the external SAML idp being kept in my Azure B2C tenant. For every user that successfully logs in, an user entry is created in my Azure B2C tenant. How can I prevent the user entries in my b2c tenant? This is my user journey:
<UserJourneys>
<UserJourney Id="UDIMASignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<!-- <ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" /> -->
<ClaimsProviderSelection TargetClaimsExchangeId="CEF" />
<ClaimsProviderSelection TargetClaimsExchangeId="UDIMA" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<!-- <ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" /> -->
<ClaimsExchange Id="CEF" TechnicalProfileReferenceId="AADCEF-OpenIdConnect" />
<ClaimsExchange Id="UDIMA" TechnicalProfileReferenceId="Saml2AssertionIssuer" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId). -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- <OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="AADCEF-OpenIdConnect" /> -->
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
```
Please check if the provided references can be worked around.
By default, for external identity, Azure AD B2C will create a user object (objectID) in its own directory .So one maybe able to store claims that are asserted by the external IdP and also end users claims or your own application.Object id that is created by this external identity can be used to be passed to next step to process and used further.
The ObjectID attribute from Azure AD is generally saved under alternateSecurityID with identityProvider.
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
</OutputClaimsTransformations>
Reference: AAD B2C querying for federated identities with the MS Graph API - Stack Overflow
Please check if the user is checked with its oid i.e; objectId and issuer (identity provider) to unlink or prevent to sign in or remove that user identiy.
<ClaimsTransformation Id="RemoveUserIdentityFromCollectionByIssuer" TransformationMethod="RemoveUserIdentityFromCollectionByIssuer">
<InputClaims>
<InputClaim ClaimTypeReferenceId="issuerToUnlink" TransformationClaimType="issuer" />
<InputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
Please check active-directory-b2c-advanced-policies/TRUSTFRAMEWORKBASE.xml · GitHub for details.
Also Azure B2c how to prevent user to login till admin approve - Stack Overflow if required.
Reference:
active-directory-b2c-advanced-policies/account-linking at master · Azure-Samples/active-directory-b2c-advanced-policies · GitHub

Azure B2C Policy for Azure AD IdP and Local Logon Problem

I have what should seem like a very common scenario yet no solution in the policy starter pack or in any of the public repo's and custom policy examples.
I have an application which is used by both internal staff and external customers. I am using B2C for this and our own Azure AD as a 'social' IdP, and local logon for external users.
The built-in functionality through the Azure Portal does not meet the requirements for numerous reasons. The external user accounts are created manually in the B2C directory and signup is prohibited. Thus, SignUpSignSignIn is unviable. The experience I am trying to achieve is:
If LocalLogon Then
Authenticate with Azure B2C Directory
Redirect to Application
Else
If AADSocialIdP Selected
Authenticate with Azure AD
If User Exists in B2C Then
Redirect to Application
Else
Create User in B2C using claims received (do not prompt for email verification)
Redirect to Application
I have resorted to using custom policies, using SocialAndLocalAccounts from the starter pack as a baseline, and have significantly modified the UserJourney so that single sign-on with AAD is achieved, the user is not prompted for their name, surname, email address, and then to verify their email address (as is the case with the built-in functionality). And, the user is properly redirected to the application. However, by creating this AAD TechnicalProfile and integrating it with the SignUpSignIn journey - though I disabled Signup through various changes in the policy pack.
However, once this is integrated, the local logon is broken. I have used the vanilla LocalAccounts policy pack and confirmed that it works and redirects to the application with the claims as expected, but once I add my AAD TechnicalProfile and ClaimsExchange in then when using local login all I get is Username or password is incorrect.
I believe this is an issue with the UserJourney I've written but at the moment I'm lost as to invoke a different journey for a local logon to a social one. I believe that my TechnicalProfile is overwriting claims during the journey which is causing this error.
My AAD TechnicalProfile is:
<TechnicalProfile Id="AAD-GB-OpenIdConnect">
<DisplayName>XXXXXXXXXXXXX</DisplayName>
<Description>XXXXXXXXXXXXX</Description>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/XXXXXXXXXXXXX/v2.0/.well-known/openid-configuration</Item>
<Item Key="client_id">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="Prompt">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_PortalAADSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
<OutputClaim ClaimTypeReferenceId="identityProviderAccessToken" PartnerClaimType="{oauth2:access_token}" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateOtherMailsFromEmail"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
</TechnicalProfile>
It is worth mentioning here that I have created a CreateOtherMailsFromEmail OutputClaimsTransformation which basically creates an emails output claim, since the application is designed to take the first element of an array of emails, as opposed to a single email address.
My UserJourney is as follows:
<UserJourney Id="CustomSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="AzureADXXXXXXXXExchange" />
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AzureADXXXXXXXXExchange" TechnicalProfileReferenceId="AAD-GB-OpenIdConnect" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
I have enabled debugging with App Insights and am examining the logs through VSCode but didn't find anything helpful.
How can this journey be adapted to support both login methods?
This issue is the result of specifying output claims with the "required" attribute set in the unified SignInSignUp relying party section. All were available in the bag through the Social IdP flow but not through the local account sign-in option.
When specifying "required" output claims you must ensure that each possible user journey will follow a technical profile chain which retrieves or adds these claims. In my specific case, the specification of claims required by the application developer included some which were not made available through the baseline technical profiles in the starter pack.
To resolve, I had to modify several technical profiles, create a unique claims transformation and apply it as an output claims transformation to the AAD-UserReadUsingObjectId baseline technical profile.
aka.ms/iefsetup
This is a very useful tool which will help to create a Social IdP and Local Login custom B2C configuration (credit #Jas Suri - MSFT). It provides a fully automated means to customise and deploy all the necessary configuration to configure a B2C tenant to use the Identity Experience Framework.
In my case this was not the entire solution for me but it helped me take a fresh look at exactly how the framework operates and eventually achieve the desired solution.

Azure AD B2C Custom SignIn Policy Displays SignUpSignIn

I have a custom SignIn policy, because I need to fetch something from an application database (via REST API) during the sign in process.
So I have this user journey
<UserJourney Id="SignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.localaccountsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetCustomDbObj" TechnicalProfileReferenceId="GetCustomDbObj" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
You can see that in step #3, I have a reference to a custom technical profile that fetches some data from a custom REST API.
In the first step, I display (supposedly) the sign in page, it references api.localaccountsignin
<ContentDefinition Id="api.localaccountsignin">
<LoadUri>https://mytenant.b2clogin.com/static/tenant/default/signin.cshtml</LoadUri>
<RecoveryUri>https://mytenant.b2clogin.com/static/tenant/default/signin.cshtml</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:1.0.0</DataUri>
<Metadata>
<Item Key="TemplateId">azureBlue</Item>
</Metadata>
</ContentDefinition>
The problem is that when I use the policy, I see a page that resembles the signup+signin unified policy
Instead of just the sign in policy, which is what I desire.
I suspect that it has something to do with
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.localaccountsignin">
More specifically, CombinedSignInAndSignUp
But I don't know what to put instead. I tried using ClaimsExchange but it didn't work.
Is there a solution?
As per this:
You can achieve this by setting SignUp to “False”.
<Item Key=”setting.showSignupLink”>False</Item>

Unable to query custom REST API within Azure AD B2C custom policy

I'm trying to call our REST API when a new user signs up by creating a custom Azure AD B2C custom policy. I've attempted adding the call to the API both as a validation profile as described at Integrate REST API claims exchanges in your Azure AD B2C user journey as validation of user input, and as a user journey as described at Walkthrough: Integrate REST API claims exchanges in your Azure AD B2C user journey as an orchestration step.
I have not been able to get either scenario to work. In both cases, I receive the following error, "AADB2C90027: Basic credentials specified for 'REST-API-SignUp' are invalid. Check that the credentials are correct and that access has been granted by the resource."
When I use Application Insights in combination with adding the call to the API as validation of user input, I see "Exception of type 'Web.TPEngine.Providers.BadArgumentRetryNeededException' was thrown." In other Stack Overflow posts, I see that the BadArgumentRetryNeededException was associated with the IdentityExperienceFramework and ProxyIdentityExperienceFramework apps not being configured correctly and/or permission not being granted from one app to the other. However, I have verified multiple times that these are configured correctly and that they are referenced correctly in the TrustFrameworkExtensions.xml file, so I assume that this is not the problem.
I have tested calling the API both with and without basic authentication, and I receive the same error. Therefore, I'm also assuming that the problem is not with the API requiring basic authentication because I receive this error even when the API requires no authentication.
I'll copy some of the custom policies below.
Code from TrustFrameworkExtensions.xml:
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="notrealtenantid.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://notrealtenantid.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>notrealtenantid.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="loyaltyNumber">
<DisplayName>loyaltyNumber</DisplayName>
<DataType>string</DataType>
<UserHelpText>Customer loyalty number</UserHelpText>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<!-- Local account claims provider -->
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<!-- ProxyIdentityExperienceFramework -->
<Item Key="client_id">732c7aec-0080-4421-bdba-cb0a156b596f</Item>
<!-- IdentityExperienceFramework -->
<Item Key="IdTokenAudience">e66b1d02-a473-4a1a-8cc2-b5d22da7d92a</Item>
</Metadata>
<InputClaims>
<!-- ProxyIdentityExperienceFramework -->
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="732c7aec-0080-4421-bdba-cb0a156b596f" />
<!-- IdentityExperienceFramework -->
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="e66b1d02-a473-4a1a-8cc2-b5d22da7d92a" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Facebook claims provider -->
<ClaimsProvider>
<DisplayName>Facebook</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH">
<Metadata>
<Item Key="client_id">1234</Item>
<Item Key="scope">email public_profile</Item>
<Item Key="ClaimsEndpoint">https://graph.facebook.com/me?fields=id,first_name,last_name,name,email</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<!-- Custom Restful service -->
<TechnicalProfile Id="REST-API-SignUp">
<DisplayName>Validate user's input data and return loyaltyNumber claim</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://NotRealUrl.com/1/0/usercheck</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="firstName" />
<InputClaim ClaimTypeReferenceId="surname" PartnerClaimType="lastName" />
<InputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="loyaltyNumber" PartnerClaimType="loyaltyNumber" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Do Rest call.
NOTE!! Sign up works if this orchestration step is not included. -->
<OrchestrationStep Order="7" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetLoyaltyData" TechnicalProfileReferenceId="REST-API-SignUp" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
Code from SignUpOrSignin.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="notrealtenantid.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin"
PublicPolicyUri="http://notrealtenantid.onmicrosoft.com/B2C_1A_signup_signin"
DeploymentMode="Development"
UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights">
<BasePolicy>
<TenantId>notrealtenantid.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="loyaltyNumber" DefaultValue="" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
If I comment out orchestration step 7, which is the step that calls our REST API, then the sign up process works. So, the problem is isolated to the calling of the API.
Does anyone know what might be causing the "AADB2C90027: Basic credentials specified for 'REST-API-SignUp' are invalid. Check that the credentials are correct and that access has been granted by the resource." error? I'm at a loss because the API is not requiring basic authentication.
After some team discussion, it turns out that the Web Application Firewall (WAF) configured on our Azure Application Gateway was blocking the request and returning an HTTP response of 403 because B2C wasn't sending an HTTP header for User-Agent.
The request worked after disabling the WAF rule that was blocking it.
A feature request was created to have B2C send a User-Agent request header at https://feedback.azure.com/forums/34192--general-feedback/suggestions/35235046-send-a-user-agent-when-an-azure-ad-b2c-custom-poli
In the meantime, a workaround is to either disable the WAF rule that requires a User-Agent header, or to have B2C call an API that is not protected by a WAF.

AAD B2C UI Customization - MS content not appearing

I'm trying to use AAD B2C Page UI customization along with Custom Policies as described here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-ui-customization-custom. I can get my content to appear, but the standard B2C content does not seem to be merged in as expected.
I have this userjourney in my Extensions.xml file:
<UserJourney Id="SignInB2CLocal">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
and I have this ContentDefinition, also in my Extensions.xml file:
<BuildingBlocks>
<ContentDefinitions>
<ContentDefinition Id="api.signuporsignin">
<LoadUri>https://kdesteasidmcustom.blob.core.windows.net/LocalSignUI.html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:idpselection:1.0.0</DataUri>
<Metadata>
<Item Key="DisplayName">Idp selection page</Item>
<Item Key="language.intro">Sign in</Item>
</Metadata>
</ContentDefinition>
</ContentDefinitions>
</BuildingBlocks>
My UI customization file (LocalSignUI.html) as referenced above is very simple:
<!DOCTYPE html>
<html>
<body>
<h1>It worked!</h1>
<div id="api"></div>
</body>
</html>
www.test-cors.org seems to think that CORS is set up correctly for my content file. When I try to run the userjourney (using the path per the Azure AADB2C portal) I get a page that displays "It worked!" (from my html content file), "Sign in" (not sure where this originates but perhaps from the ContentDefinition "language.intro" key), and nothing else - rest of page is blank. If I examine page source, there is lots of javascript and a few tags to cover no-cookie and no-javascript scenarios, but there are no form or input tags. I've looked at the log info in application insights and see a reference to my content file but no apparent errors.
Any ideas?
thanks!
Martin
Solved thanks to hint from #spottedmahn! In my TrustFrameworkExtensions.xml file, I created a ContentDefinition (within BuildingBlocks -> ContentDefinitions) like this:
<ContentDefinition Id="api.signuporsignin">
<LoadUri>URL-OF-MY-CUSTOMIZATON-FILE</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:unifiedssp:1.0.0</DataUri>
<Metadata>
<Item Key="DisplayName">Signin and Signup</Item>
</Metadata>
</ContentDefinition>
The UserJourney's first orchestration step is of Type CombinedSignInAndSignUp and ContentDefinitionReferenceID of api.signuporsignin. It appears that
the urn:com:microsoft:aad:b2c:elements:unifiedssp:1.0.0 value for DataUri in the ContentDefinition is key. The Div tag in my customization file contains simply id="api".
thanks!
Martin
I think you need at least a <head><title></title></head> otherwise it's not well formed HTML and may not work.

Resources