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.
Related
Can anyone explain to me (before Azure B2C Custom Policies make me pull what's left of my hair out), why this technical profile fails to ever return an "objectId" when a user account exists in Azure B2C. I am collecting the email claim in a previous screen and calling the technical profile from the orchestration step.
I can see the profile executing in my Application Insights logs and I have confirmed that the email address I use in the claim is in the directory. But every time, no matter which email address I use, I never get an objectId back which means I can never detect if the user exists or not!
Technical Profile
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Orchestration Step
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<!-- Skip this if we already have an object id from single signon -->
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectIdFromSession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<!-- Call a technical profile to see if an account can be found with the email supplied in AD -->
<ClaimsExchange Id="AccountExistsClaim"
TechnicalProfileReferenceId="UE-AAD-CheckAccountExistsByEmail" />
</ClaimsExchanges>
</OrchestrationStep>
You have specified ClaimsTransformationProtocolProvider as the handler.
You need the AAD provider as the handler to make Graph API queries.
Though if AAD-Common already has the Protocol element, you don't need to specify it here again since it'll be included from there.
Like so:
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<!-- You don't actually need this though if AAD-Common has it -->
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
ClaimsTransformationProtocolProvider is used for running claims transformations to produce new claims or modify existing claims.
It is not used for querying AAD.
IN our forgot password flow, I want to do a REST API call to do a validation check on the account. We have a database that we need to do a check against.
During this process, the API will return a 200 or 400.
If its a 200, I want them to continue on with the orchestration of forgot password.
If it is a 400, instead of pulling them out of the flow and putting them back in the application, I would rather show them that there is a problem with their account and to contact our support or to register a new account.
However, no matter what the response, in my current code it is send them along in the orchestration step.
This is the orchestration step:
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetMemberClaimsExchange" TechnicalProfileReferenceId="MemberAccountHolderCollector" />
</ClaimsExchanges>
</OrchestrationStep>
then this is my claims provider.
<ClaimsProvider>
<DisplayName>REST API to Check Member Status</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="MemberAccountHolderCollector">
<DisplayName>Collect Member Info Technical Profile</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.register</Item>
</Metadata>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-CheckMemberAccountHolder" ContinueOnError="true"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="REST-CheckMemberAccountHolder">
<DisplayName>Rest API call to Check Member status</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{API}</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailaddress"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailaddress" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
The api.selfasserted.register is simply a template i created. I don't need to use it. I could just throw the error on the screen as anything, but I was just trying anything to get it to work.
Any help is appreciated here.
Edit:
Thank you for the advice, so i think i understand where your going.
I added a claim type
<ClaimType Id="requireRegister">
<DisplayName>requireRegster</DisplayName>
<DataType>boolean</DataType>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
The idea would be that I can do an output claim.
The problem is when i do this...
<TechnicalProfiles>
<TechnicalProfile Id="MemberAccountHolderCollector">
<DisplayName>Collect Member Info Technical Profile</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.register</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="requireRegister" DefaultValue="true"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-CheckMemberAccountHolder" ContinueOnError="true"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
It shows the page every time, which i don't want to do either...
Edit 2:
Our forgot password is done off this policy
[enter link description here][1]
[1]: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-reset-policy?pivots=b2c-custom-policy
in our policy when you click the forgot password button it invokes the subjourney
<SubJourneys>
<SubJourney Id="PasswordReset" Type="Call">
<OrchestrationSteps>
<!-- Validate user's email address. -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show TOU-->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedConsentExchange" TechnicalProfileReferenceId="SelfAsserted-PasswordResetConsent" />
</ClaimsExchanges>
</OrchestrationStep>
There is a custom page to show our TOU consent, before they get there, I need the account to run through our validation before.
Edit 3:
Doing the code below skips the Email validation step in the forgot password policy.
<ClaimsProvider>
<DisplayName>REST API to Check Member</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-CheckMemberAccount" ContinueOnError="false"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!--Not using anymore -->
<TechnicalProfile Id="MemberAccountHolderCollector">
<DisplayName>Collect Member Info Technical Profile</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.register</Item>
</Metadata>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-CheckMemberAccount" ContinueOnError="false"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="REST-CheckMemberAccount">
<DisplayName>Rest API call to Check Member status</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{API}</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailaddress"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailaddress" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
This configuration, where there are no output claims as part of a selfAsserted technical profile, will cause B2C to skip this entire step.
You should be adding this as a validation technical profile on the previous step, where the user enters and validates their email. As follows:
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<DisplayName>Reset password using email address</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="requireRegister" DefaultValue="true"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
<ValidationTechnicalProfile ReferenceId="MemberAccountHolderCollector" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Remove MemberAccountHolderCollector technical profile entirely. And any reference to that from your User Journey.
Now after the user validates their email, and submits the page, the REST API will be called. If it returns an error, it is displayed on the same screen where the user validated their email.
You must return a HTTP 409 conflict with proper error JSON payload for it to be displayed on the screen.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/restful-technical-profile#returning-validation-error-message
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>
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.
I am developing a RESTful Service, which gets called during the Signup/Signin Process in Azure AD B2C. My Service logs state, that data successfully arrives, and output-claims (customerId) get created.
But I receive the following error message, and the user doesn't get created:
AADB2C90161 A self-asserted send response has failed with reason (Internal Server Error).
Correlation ID 7eac5fd2-cd85-4535-b166-4cc8f0264d07
I have oriented myself towards this example: https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/scenarios/aadb2c-ief-rest-api-netfw/
Did anyone experience similar issues and has a hint what could be the problem in my case?
in TrustFrameworkExtension:
<ClaimsProvider>
<DisplayName>KTM REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-API-SignUp">
<DisplayName>Generate and return customerID 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://<my.service.com>/api/Identity/Signup</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="Email" />
<InputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="FirstName" />
<InputClaim ClaimTypeReferenceId="surname" PartnerClaimType="LastName" />
<InputClaim ClaimTypeReferenceId="testClaim" PartnerClaimType="ObjectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="customerId" PartnerClaimType="CustomerId" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="customerId" PartnerClaimType="CustomerId" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-API-SignUp" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
In this case, you have a run-time error. The claim type "customerId" is defined as a string in the policy, but looks like the value coming over the wire (where partnerClaimType is "CustomerId") is a number, so the system is unable to map it. See this line:
<OutputClaim ClaimTypeReferenceId="customerId" PartnerClaimType="CustomerId" />
This is how a Rest API will return a string vs a number (note the absence of quotes in a number):
{
"name": "John",
"age": 24
}
While this message can be improved, you should configure your policy to collect logs using Application Insights. This will allow you to debug similar run-time issues more easily.