Azure B2C: Adding an orchestration step to ask for more information - azure-ad-b2c

I'm looking to break up our registration journey into multiple pages, so we don't end up with a massive form.
I'm trying to add an orchestration step after the initial registration page to ask for the user's favourite colour.
I have added the following claims provider:
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SATP-GetFavouriteColour">
<DisplayName>Local Account Sign In</DisplayName>
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.signuporsignin</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="favouriteColour" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
and have updated my SignUpOrSignIn journey to include it just before the final step of returning the claims to the RP, like so:
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetFavouriteColour" TechnicalProfileReferenceId="SATP-GetFavouriteColour" />
</ClaimsExchanges>
</OrchestrationStep>
I have also added the output claim to my Relying Party file like so:
<OutputClaim ClaimTypeReferenceId="favouriteColour" DefaultValue="Lemons"/>
The policy files validate and upload successfully, but when I go through the journey, I simply get the default value of "Lemons" returned to my RP.
I expected B2C to ask the user for their favourite colour. Why isn't B2C asking the user for the new field I added?
Am I right in thinking this is possible, and I'm just missing something simple?
Thanks in advance

I found the cause of this, I had missed off <UserInputType>TextBox</UserInputType> from my claimType declaration

Related

Azure B2C custom policy - Is there a way to display a claim in a ClaimsProviderSelection orchestration step

I am currently working with Azure B2C custom policies for my Auth flow.
I have a ClaimsProviderSelection orchestration step which shows the user two options:
Send code to their MFA email saved in authentication methods
Lost Email
What I would like to do is show the users email address through the use of a ClaimProvider in either the display text, or the button itself (see below)
If this is not possible, then I would love to be able to add a 'lost email' button on the verification control page itself - like so:
From what I have seen though, it seems this is only available with 'ForgotPasswordExchange' (as seen here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-reset-policy?pivots=b2c-custom-policy) for passwords and not authentication methods.
If anyone has any experience with customizing ClaimsProviderSelection steps, or adding custom links on orchestration steps your help would be greatly appreciated!
See below for code examples:
Orchestration step:
<OrchestrationStep Order="2" Type="ClaimsProviderSelection" ContentDefinitionReferenceId='api.MFAselections' >
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>strongAuthenticationEmailAddress</Value>
<Value>strongAuthenticationPhoneNumber</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="MFAVerifyEmailAddress" />
<ClaimsProviderSelection TargetClaimsExchangeId="LostEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
Technical Profile:
<TechnicalProfile Id="MFA_VerifyEmailAddress">
<DisplayName>SEND TO {Claim:strongAuthenticationEmailAddress}
</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">MFAVerifyEmail</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<!-- <Item Key="setting.showContinueButton">false</Item> -->
<Item Key="setting.showCancelButton">false</Item>
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded the number of retries allowed.</Item>
<Item Key="UserMessageIfInvalidCode">You have entered the wrong code.</Item>
<Item Key="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="true" AlwaysUseDefaultValue='true'/>
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="email" AlwaysUseDefaultValue='true' />
<OutputClaim ClaimTypeReferenceId="isLostEmail" DefaultValue="false" AlwaysUseDefaultValue='true' />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
For anyone who is coming across this - this is what I ended up doing:
Add ContentDefinitionParameters with claim to UserJourneyBehaviors in your RelyingParty
<ContentDefinitionParameters> <Parameter Name="email">{Claim:maskedEmail}</Parameter> </ContentDefinitionParameters>
Use JS to grab email claim from source code, and insert to HTML
const parser = new URL(SETTINGS.remoteResource); let email = parser.searchParams.get('email');
Have you tried to do Output Claims transformation on the email, create a claim of type string, then append the email to it, in a previous step. And display that on the screen.

Azure B2C - Send Groups In Claims to SAML SP Using REST API That Queries Graph

I am trying to return group claim in a SignupSign custom policy in Azure B2C to the SAML test app found here https://samltestapp2.azurewebsites.net/. Here's of what I have done so far: -
Created a HTTP trigger PowerShell Azure Function that returns a comma-separated list of groups a user belongs to after querying the MS Graph API. I call it outside B2C and it works fine.
In the TrustFrameworkExtensions.xml file, added a claim type element like so
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="groups">
<DisplayName>Comma delimited list of group names</DisplayName>
<DataType>stringCollection</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="groups" />
<Protocol Name="OpenIdConnect" PartnerClaimType="groups" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/groups" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
In the TrustFrameworkExtensions.xml I also added a Technical profile and an orchestration step like so
<TechnicalProfile Id="GetUserGroups">
<DisplayName>Retrieves security groups assigned to the user</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://morphitgraphapicall.azurewebsites.net/api/GetGroupsFromGraph?objectID=objectId</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim Required="true" ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="groups" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
...
<OrchestrationStep Order="7" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetUserGroups" TechnicalProfileReferenceId="GetUserGroups" />
</ClaimsExchanges>
</OrchestrationStep>
In the relying party file, added an output claim like so
relying party file output claim
But I can't seem to get the groups claim passed to the SP application. I can tell that the REST API is being called because being on the Azure Functions consumption plan, there is a cold start if I haven't called the function for a bit which leads to a pause after sign-in.
Thank you.

How can I read the mobile value from a B2C user record and transform it to a strongAuthenticationPhoneNumber?

I have the need to create Azure B2C user accounts programmatically. In a separate user data store I hold pertinent information about the users I need to set up in B2C including their mobile phone number, which we've already been communicating with them on.
My business requirement is that this mobile phone number is used as a secondary factor during the user's first-time login/password reset experience. I have an initial login experience which uses an externally-created JWT token to take the user to a custom User Journey where they can set a password for the first time.
I understand that it is not yet possible to set the Azure MFA mobile number via Graph API or PowerShell. (Is this still true?). Therefore B2C asks the user to enter their mobile number in the exemplar PhoneFactor-InputOrVerify Technical Profile. This is a security hole as you can just enter any mobile number in there and verify that number.
I can easily programmatically add the user's number to some other field - e.g. the mobile field on the user record.
Question 1.
Is there a way to read the user account mobile value and present it to a Technical Profile as if it is the strongAuthenticationPhoneNumber value or Verified.strongAuthenticationPhoneNumber?
Question 2.
Is this even a good idea? I imagine there are good reasons not to do this, but I can't fathom what they might be.
I've tried creating new ClaimTypes, reading the 'mobile' field value, creating ClaimsTranfromations to try to make the mobile claim appear to be the strongAuthenticationPhoneNumber claim, and trying generally to 'spoof' B2C into thinking this is the actual number as stored in the MFA data store.
This is the the standard PhoneFactor-InputOrVerify Technical Profile from the starterpack:
<ClaimsProvider>
<DisplayName>PhoneFactor</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.phonefactor</Item>
<Item Key="ManualPhoneNumberEntryAllowed">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserIdForMFA" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I can provide more code samples of the custom User Journey I mentioned earlier but I don't think this will help with this problem.
You have a few options:
You can add the strongAuthenticationPhoneNumber claim to the same JWT that is used for the onboarding flow and prompt for verification of this phone number during this onboarding flow.
You can map to the strongAuthenticationPhoneNumber claim for the PhoneFactor-InputOrVerify technical profile from the mobile property (or an extension property) of the user object.
For option 1, the onboarding user journey should write the verified phone number to the user object, without the newPhoneNumberEntered-based precondition:
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
For option 2, you can map to the strongAuthenticationPhoneNumber claim from the mobile property, as follows:
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="mobile" PartnerClaimType="strongAuthenticationPhoneNumber" />
</InputClaims>

Azure AD B2C - Refresh_Token refresh claims via REST (Identity Experience Framework)

We have Azure AD B2C setup to use Identity Experience Framework, and on sign-in/sign-up a REST call is made to get extra security credential claims via an Azure Function. This works fine.
When we request an Access/Id Token via Refresh_Token via Azure AD B2C it looks like we get the same token back, and it doesn't call the REST API to get the latest updated token claims. Is it possible to make change this User Journey so it does?
Is there another solution to refresh token without logging in again to get latest updates?
(We could get around this in code and not using the Token, but for various reasons we want to explore this first).
You can declare a refresh token user journey, which calls your REST API, as follows:
<UserJourney Id="TokenRefresh">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RefreshTokenExchange" TechnicalProfileReferenceId="TpEngine_RefreshToken" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- TODO: Add an orchestration step that calls the REST API. -->
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
The initial orchestration step invokes the TpEngine_RefreshToken technical profile that reads the objectId claim from the current refresh token:
<ClaimsProvider>
<DisplayName>Trustframework Policy Engine Technical Profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TpEngine_c3bd4fe2-1775-4013-b91d-35f16d377d13">
<DisplayName>Trustframework Policy Engine Default Technical Profile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="url">{service:te}</Item>
</Metadata>
</TechnicalProfile>
<TechnicalProfile Id="TpEngine_RefreshToken">
<DisplayName>Trustframework Policy Engine Refresh Token Technical Profile</DisplayName>
<Protocol Name="None" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The second orchestration step invokes the AAD-UserReadUsingObjectId technical profile that reads claims from the Azure AD B2C directory for the signed-in user by the objectId claim.
Another orchestration step can call your REST API.
The final orchestration step issues new tokens.
You must reference the TokenRefresh user journey using the RefreshTokenUserJourneyId metadata item with the JwtIssuer technical profile so that tokens that are issued by this technical profile are refreshed by this user journey:
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
<Item Key="RefreshTokenUserJourneyId">TokenRefresh</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>

How to prompt for new claims for a new application using the same Azure AD B2C tenant

I have an existing application "A" using an Azure AD B2C tenant. During registration users have been asked to enter a number (specific for this application "A") that is stored in a Claim with the name "NumberA".
Now I want to create an new application "B" and I want the existing users of my tenant to be able to log into the application "B". But before they can use it they have to be prompted to enter a new number (specific for application "B") that is stored in a Claim with the name "NumberB".
When new users of application "B" register themselves they only have to enter the number for "B".
I think this must be possible but I am not sure how to do this.
Create a new Custom Policy "B2C_AppB_signup_signin"?
And then add a new Claim "NumberB" in a new "Extensions" file and "override" the technical profiles (AAD-UserWriteUsingLogonEmail, AAD-UserReadUsingEmailAddress etc)
Or is this the wrong path..
You are on the right track.
This can be implemented by creating two user journeys -- one for Application A and another for Application B -- and then adding a ClaimsExist precondition to an orchestration step in both user journeys that prompts for the application-specific claim.
For example: For Application B's sign-up or sign-in user journey, you can add the following orchestration step after the user object is read from Azure Active Directory (after either the end user has signed in with an existing account or signed up with a new account), which checks whether the "extension_NumberB" claim exists for this user object and if not then prompts for it:
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>extension_NumberB</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedApplicationBRegistrationExchange" TechnicalProfileReferenceId="SelfAsserted-ApplicationB-Registration" />
</ClaimsExchanges>
</OrchestrationStep>
Then add the "SelfAsserted-ApplicationB-Registration" technical profile:
<TechnicalProfile Id="SelfAsserted-ApplicationB-Registration">
<DisplayName>Application B Registration</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted.applicationb.registration</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_NumberB" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteProfileUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
You will then have to add the "extension_NumberB" claim as an <OutputClaim /> for the "AAD-UserReadUsingObjectId" technical profile and it as a <PersistedClaim /> for the "AAD-UserWriteProfileUsingObjectId" technical profile.
The option outlined is good although you have to manage to 2 policies and if you decide to have a 3rd or a 4th client the more policies you will have to manage.
I would suggest you create a rest call to a function app that accepts the client_id of the app {OIDC:ClientId} and then returns the value of a claim based on it.
That way you only ever have one policy and then you can modify the function app rather than a policy
I have detailed this approach here
Get the Azure AD B2C Application client id in the custom policy

Resources