ADB2C Rest API not save custom attribute to profile - azure-ad-b2c

I enabled custom policy in ADB2C and to add more claims I used a rest API to return data for the custom attribute
the response from RESTAPI looks like below :
{
"version" : "1.0.0",
"action" : "Continue",
"extension_<client_id>_empid" : 15,
"extension_empid" :15,
}
and in the claims, I can read this value but actually this value not saving to the user profile so when I fetch the data using graph it's not coming back How I can override this value to user profile

Assuming extension_empid claim is defined like this:
<ClaimType Id="extension_empid">
<DisplayName>Employee ID</DisplayName>
<DataType>long</DataType>
</ClaimType>
it seems obtaining(parsing from JSON) claim is missing from your REST API, in Restful technical profile OutputClaims collection should contain the element:
<TechnicalProfile Id="getEmployeeInfoAPI">
<DisplayName>Get Employee info</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="extension_empid" PartnerClaimType="extension_empid" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
And finally in AAD-UserWriteUsingLogonEmail technical profile, PersistedClaims element should contain the element to persist the data to the directory:
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
...
<PersistedClaims>
...
<PersistedClaim ClaimTypeReferenceId="extension_empid" />
</PersistedClaims>
</TechnicalProfile>

Related

Unable to get ValidationTechnicalProfile to valid input

I am trying to setup the use of a one time pass code (OTP) on an Azure B2C Custom Policy. I have a working set of Orchestration steps that extract an email from a claims token supplied in the URL and then mail a randomly generated code to that email. I know there are "VerifyCode" and "GenerateCode" technical profiles available, but they rely on the user entering the email into a display field first, which I want to avoid.
I am unable get a ValidationTechnicalProfile to fire so that it can execute a ClaimsTransformation to the two claims values. These are the generated OTP mailed to the user and the input collected from a TechnicalProvider that uses a SelfAssertedAttributeProvider to display the input field with a ContentDefinition.
I am basing my code on this article and also a walkthrough with regard to ValidationTechnicalProfiles
Please could someone explain why the ValidationTechnicalProfile appears to either be skipped or is failing to work?
Claims
<ClaimType Id="Otp">
<DisplayName>One-time password</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="VerificationCode">
<DisplayName>Secondary Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Enter your verification code</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
Orchestration Step
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-OTP-Exchange" TechnicalProfileReferenceId="SelfAsserted-EnterOTP" />
</ClaimsExchanges>
</OrchestrationStep>
Technical Profile
Uses the api.page.codeinput content definition to collect the value of the VerificationCode claim in an input box. Once collected, it should fire the Self-AssertedOTPCompare TechnicalProfile as a ValidationTechnicalProfile
<TechnicalProfile Id="SelfAsserted-EnterOTP">
<DisplayName>Enter OTP</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.page.codeinput</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">Invalid OTP Code</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="VerificationCode" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Self-AssertedOTPCompare" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Validation Technical Profile
Executes the AssertSuppliedAndGeneratedOTPAreEqual ClaimsTransformation
<TechnicalProfile Id="Self-AssertedOTPCompare">
<DisplayName>Returns the result from comparing the generated OTP with the supplied on</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="VerificationCode" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertSuppliedAndGeneratedOTPAreEqual" />
</OutputClaimsTransformations>
</TechnicalProfile>
Claims Transformation
Should compare the two claims and raise an exception to prevent the user continuing if the codes are different.
<ClaimsTransformation Id="AssertSuppliedAndGeneratedOTPAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="Otp" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="VerificationCode" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
</InputParameters>
</ClaimsTransformation>
Logging with AppInsights I think it's hitting the technical profile but control appears to be returning to the orchestration. If I remove the ValidationTechnicalProfiles section from the SelfAsserted-EnterOTP technical profile, the flow stops at the page where the user would enter their code.
{
"Kind": "HandlerResult",
"Content": {
"Result": true,
"RecorderRecord": {
"Values": [
{
"Key": "InitiatingClaimsExchange",
"Value": {
"ProtocolType": "Identity Experience Engine API",
"TargetEntity": "Generate-OTP",
"TechnicalProfileId": "SelfAsserted-EnterOTP",
"ProtocolProviderType": "SelfAssertedAttributeProvider"
}
}
]
},
"PredicateResult": "True"
}
},
{
"Kind": "Action",
"Content": "Web.TPEngine.StateMachineHandlers.SwitchToApiOrchestrationHandler"
},
{
"Kind": "HandlerResult",
"Content": {
"Result": true
}
},
{
"Kind": "Transition",
"Content": {
"EventName": "SELFASSERTED",
"StateName": "AwaitingNextStep"
}
},
Additional
The original implementation above caused the process to never show the input field to collect the OTP from the user - jas-suri-msft suggests this is because a ValidationTechnicalProvider does not bubble up exceptions.
If this is the case, the documentation appears to be wrong:
The AssertStringClaimsAreEqual claims transformation is always
executed from a validation technical profile that is called by a
self-asserted technical profile, or a DisplayControl. The
UserMessageIfClaimsTransformationStringsAreNotEqual metadata of a
self-asserted technical profile controls the error message that is
presented to the user.
I have tried changing the ValidationTechnicalProfiles to a OutputClaimsTransformations node as suggested.
I replaced this:
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Self-AssertedOTPCompare" />
</ValidationTechnicalProfiles>
with this:
<OutputClaimsTransformations>
<OutputClaimsTransformation
ReferenceId="AssertSuppliedAndGeneratedOTPAreEqual" />
</OutputClaimsTransformations>
This caused the OTP collection screen to show but the page to show no error message if the code was wrong and go no further. This is despite setting UserMessageIfClaimsTransformationStringsAreNotEqual in the metadata However, entering the correct code allowed the steps to progress.
So how to do I get the page to show the exception message?
SOLUTION
As mentioned in the comments, I am unable to remove the OutputClaims from the ValidationTechnicalProfile as the policy will not validate. However, changing the OutputClaim to a DisplayClaim resolves the issue.
<TechnicalProfile Id="SelfAsserted-EnterOTP">
<DisplayName>Enter OTP</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.page.codeinput</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">Invalid OTP Code</Item>
</Metadata>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="suppliedOTP" Required="true" />
</DisplayClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Self-AssertedOTPCompare" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
When you call a claims transformation technical profile from the validation technical profile section, the error is not bubbled up like when called directly as an OutputClaimsTransformation. So i suspect your observation is that, any code entered works and proceeds to the next step in the journey.
Instead, call AssertSuppliedAndGeneratedOTPAreEqual as an OutputClaimsTransformation directly from SelfAsserted-EnterOTP.

Custom policy: Invalidate B2C session after account deletion

We have a custom policy to allow users to delete their accounts, following something similar to https://github.com/azure-ad-b2c/samples/blob/master/policies/delete-my-account/policy/DeleteMyAccount.xml
If we then invoke another custom policy the B2C session is still active. The technical profile below still pulls out the old (deleted) objectId.
<TechnicalProfile Id="SM-AAD">
<DisplayName>Session Mananagement Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectIdFromSession" DefaultValue="true" />
</OutputClaims>
</TechnicalProfile>
I see references to invalidateAllRefreshTokens invalidating the browser session, but then see many discussion saying this is not the case.
If there still not a way to invalidate the session, or call logout from a custom policy.
Thanks
Nigel.

How to do a two way synchronization of user credential (Microsoft AD B2C) with a proprietary database

We need to see the feasibility of synchronization of user credentials
between the Microsoft AD B2C and proprietary database in real time.
If it is feasible do you have any pointers on 'how to'.
What is the hashing algorithm used in the MS AD B2C credentials? is
there a ay to customize it?
Use a REST API call during the Sign Up and Password Reset authentication journey's to send the captured password claim to your API. Your API can then update your DB to keep it in sync.
You can call your REST API and can send any claims captured in the journey using this guide.
In this case, since you are looking to send a password, you must call your REST API as part of a validation technical profile, instead of directly as an orchestration step.
You can send the plaintext password to your API.
Here is an outline of what you need to configure:
In the sign up technical profile, add a call to your REST API as a validation technical profile.
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
...
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
<ValidationTechnicalProfile ReferenceId="REST-SyncPasswordToDB" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Create the REST API technical profile to send the password to your endpoint:
<TechnicalProfile Id="REST-SyncPasswordToDB">
<DisplayName>REST API call to communicate with Legacy DB</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://myApi.com/api/syncPassword</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email" />
<InputClaim ClaimTypeReferenceId="password" />
</InputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

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.

Azure B2C - Custom policies / Add registration step

I'm trying to create a custom policy that will add additional step during the sign up/in process to requires more information.
I added:
1. New ClaimsProvider
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="GetCity">
<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.selfasserted</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="city" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
New claim type
city
string
city
TextBox
New output claim in SignupSignIn RP
Additional steps before send claims during the UserJourney - SignUpOrSignIn
After the registration there is no authentication and there is no step for "city" data.
Any idea where is the problem? I already configured app insight, but there is no errors there.

Resources