Orchestration Steps Precondition works on OR logic not on AND - azure-ad-b2c

I have an Orchestration Step which works on some precondition, even if one of the precondition is satisfied the orchestration step gets skipped but I want it to work the other way.
If any of the Precondition is not satisfied I want that Orchestration Step to be executed.
Below is my orchestration step where if remember me checkbox is not checked (kmsiValue) then I want to run the orchestration step irrespective of other preconditions but I am not able to achieve that.
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>kmsiValue</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isLastMFATimeGreaterThanWindow</Value>
<Value>False</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify-MFATimeWindow" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
</ClaimsExchanges>
</OrchestrationStep>
Update :
Sorry but I am a bit confused now , I already have a TP for Remember me , the value changes accordingly on checkbox selection
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<Metadata>
<Item Key="setting.enableRememberMe">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="kmsiValue" DefaultValue="{Context:KMSI}" AlwaysUseDefaultValue="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="kmsiValue" DefaultValue="{Context:KMSI}" AlwaysUseDefaultValue="true" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The question I have is how can I split my orchestration steps now to make it work?

I would split this into two sub journeys. One subjourney for KMSI=True, and another subjourney for KMSI=False. Then in this subjourney design the orchestration steps. Where KMSI=true, the orchestration step contains your other preconditions. Where KMSI=false, your orchestration step contains no preconditions.

One way is to put all the logic into a REST API and return T or F depending on whether you want the step skipped.
The other way is to have a step that just checks KMSI. If that results in the TP being run, set a flag.
In the next step, check the flag and skip if the TP has been run. Otherwise, run the other steps.
Update
#Jas is a good idea but set a flag in the first step e.g.
<OutputClaim ClaimTypeReferenceId="kmsi" DefaultValue="true" AlwaysUseDefaultValue="true"/>
and use a precondition "claimsequal" in the second step.
"kmsi" should be boolean.

Related

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.

MFA prompt after a certain time elapses

We are using B2C Custom Policies in production and have a requirement that after a certain time elapses (20 minutes + depending on the application) the user is prompted to login with MFA again irrespective of which service the user is logging into. One of our developers stated that we could use the max_age query string parameter to achieve this and I thought to post here to see if anyone has experience in using this with Azure B2C Custom Policies or could recommend another solution? I found this link but not much else https://github.com/MicrosoftDocs/azure-docs/issues/51307
We are currently using the following MFA method in our policies, slightly modified to remove email verification as we don’t require this : https://github.com/azure-ad-b2c/samples/tree/master/policies/mfa-email-or-phone
Edit
Hi #Jas I've had time to look into the solution but had an issue that I hope you could answer.
We've been able to store the last time the user did MFA in the session rather than in an extension attribute. At first we couldn't get the OutputClaimsTransformation "CompareTimetoLastMFATime" to run after the first login however we found removing in the technical profile "MFAReadStoredMFATime" shown in the code below. Could you please let us know why including SM-MFA blocks the claimstransformation from running on subsequent logins? We see that step 16 is run in the logs however no claimstransformation and no CompareTimetoLastMFATime is output therefore the user always skips MFA.
<OrchestrationStep Order="16" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="MFAReadStoredMFATime" TechnicalProfileReferenceId="MFAReadStoredMFATime" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="17" Type="ClaimsExchange">
<Preconditions>
<!--Sample: If the preferred MFA method is not 'phone' skip this orchestration step-->
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>extension_mfaByPhoneOrEmail</Value>
<Value>phone</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>mfa_required</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>MFADoneByFedIdp</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isLastMFATimeGreaterThanWindow</Value>
<Value>False</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
</ClaimsExchanges>
</OrchestrationStep>
<TechnicalProfile Id="MFAReadStoredMFATime">
<DisplayName>Fixt the session username issue</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="LastMFATime" DefaultValue="2018-10-01T15:00:00.0000000Z" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isLastMFATimeGreaterThanWindow" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CompareTimetoLastMFATime" />
</OutputClaimsTransformations>
</TechnicalProfile>
<ClaimsTransformation Id="CompareTimetoLastMFATime" TransformationMethod="DateTimeComparison">
<InputClaims>
<InputClaim ClaimTypeReferenceId="LastMFATime" TransformationClaimType="firstDateTime" />
<InputClaim ClaimTypeReferenceId="systemDateTime" TransformationClaimType="secondDateTime" />
</InputClaims>
<InputParameters>
<InputParameter Id="operator" DataType="string" Value="earlier than" />
<InputParameter Id="timeSpanInSeconds" DataType="int" Value="100" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isLastMFATimeGreaterThanWindow" TransformationClaimType="result" />
</OutputClaims>
</ClaimsTransformation>
Something similar. I assume you mean 20-minute gap between logins?
Look in the samples - there are some examples of password reset that show you how to handle the date/time. In particular, there's one for a reset after 90 days so you could do something similar for MFA.
You can use the resolvers to get the query string value.
The main problem we had is that the query string is a string and there is no method to convert to date/time so we had to do that in an API.
Also, see this.
There is a sample here to force mfa after a certian time has surpassed.
https://github.com/azure-ad-b2c/samples/tree/master/policies/mfa-absolute-timeout-and-ip-change-trigger

Azure AD B2C Validation Profile for non-SelfAsserted Technical profiles

From testing, it appears Validation Technical Profiles are only used when added to SelfAssserted Technical Profiles
E.g the following:
<TechnicalProfile Id="ExternalIDP">
<DisplayName>Some External IdP</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<!-- ... -->
</Metadata>
<OutputClaims>
<!-- ... -->
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="FETCH-MORE-CLAIMS" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
does not appear to call the FETCH-MORE-CLAIMS profile after authenticating to the external identity provider.
Is this correct, and if so, is there another way to always force a second technical profile to be called whenever a particular technical profile is called?
One possible way would be to set an output claim that indicates that was done, and then have an orchestration step after that with a condition on that claim, which then runs your TP as a claims exchange.
So an output claim like:
<OutputClaim ClaimTypeReferenceId="idp" DefaultValue="ThisIdp" AlwaysUseDefaultValue="true" />
You'd need to define that claim if it isn't already defined, or you can use another one you already have.
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>idp</Value>
<Value>ThisIdp</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="FetchMoreClaimsExchange" TechnicalProfileReferenceId="FETCH-MORE-CLAIMS" />
</ClaimsExchanges>
</OrchestrationStep>
This orchestration step is skipped if idp != ThisIdp, so it would only run if your external idp was used.

Skip self-asserted orchestration step if claims exist not working

This is in reference to custom policy signupsignin user journey.
I have a self-asserted orchestration step that collects users email address before the combined signupsignin orchestration step.
I'm wanting to skip this step if the users claims already exist
This is necessary when collecting refresh tokens.
Here's how I have the orchestration step coded.
<OrchestrationStep Order="1" Type="ClaimsExchange" ContentDefinitionReferenceId="api.selfasserted.email">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedEmailExchange" TechnicalProfileReferenceId="SelfAsserted-CollectEmailAddress-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
I believe this might be happening because the SelfAsserted-CollectEmailAddress-NoError technical profile isn't "participating" in the SSO session; therefore it prompts the same user in the same session for the e-mail address again.
For the SelfAsserted-CollectEmailAddress-NoError technical profile to participate in the SSO session, ensure this technical profile contains the <UseTechnicalProfileForSessionManagement /> element as follows:
<TechnicalProfile Id="SelfAsserted-CollectEmailAddress-NoError">
...
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-NoError" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

Is it possible to display the attributes according to the IDP selected while signUp

We have configured Facebook, LinkedIn, Twitter, Google and AzureAD as our providers using Custom SignUp/SignIn Policy.
We have a requirement to ask for Business Email when a user sign's up using social Idp(Facebook, LinkedIn, Twitter, Google) but not when Azure AD selected(as they are already using their business email).
I have added the new attribute to this section,
<TechnicalProfile Id="SelfAsserted-Social">
Please suggest.
Firstly, you must ascertain whether an enterprise account or a social account has been logged in.
For example:
<TechnicalProfile Id="ContosoProfile">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="contosoAuthentication" />
</OutputClaims>
</TechnicalProfile>
and:
<TechnicalProfile Id="Facebook-OAUTH">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
</OutputClaims>
</TechnicalProfile>
Secondly, you must create two technical profiles for the account registration: one technical profile for the enterprise account registration that excludes the business email and the other technical profile for the social account registration that includes it.
For example:
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-Enterprise">
...
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-Social">
...
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Thirdly, based on the authenticationSource claim, you must invoke one or the other technical profile.
For example:
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Enterprise" TechnicalProfileReferenceId="SelfAsserted-Enterprise" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>contosoAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</UserJourney>

Resources