How do I make the email address (signInName) read only for the sign-in form - azure-ad-b2c

I am creating an Azure AD B2C custom policy for inviting users to user my applications. As part of the invite I set custom user attributes.
In my user journey, if the user exists I send them to a sign in screen with the email pre-populated.
How do I make the email address (signInName) field read only so that the user can't apply the invite to a different account?
This is the technical profile I have for signing in. The signInName is populated by my invite token, but it is not read only.
<TechnicalProfile Id="LocalAccountSigninWithReadOnlyEmail">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.showSignupLink">false</Item>
<Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" Required="true" PartnerClaimType="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopySignInNameToReadOnly" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

Create a readonly field.
<ClaimType Id="readOnlyEmail">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<UserHelpText/>
<UserInputType>Readonly</UserInputType>
</ClaimType>
Copy email via a claims transformation e.g.
<ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="mail" TransformationClaimType="inputClaim"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
Invoke the transformation e.g. <InputClaimsTransformations>
and then use the readonly field in the screen.

I had to face the same problem, I mean read-only email for sign-in custom flow. First of all, you have to create read-only custom claim:
<ClaimType Id="ReadOnlyEmail">
<DisplayName>Verified Email Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
Next, you have to create two custom claims transformations:
<ClaimsTransformation Id="CopyEmailToReadOnlyEmail" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CopyEmailToSignInName" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
And finally it is custom claims provider for sign-in with read-only email:
<ClaimsProvider>
<DisplayName>Local Account Sign In</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Password">
<DisplayName>Local Account Signin</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>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyEmailToReadOnlyEmail" />
<InputClaimsTransformation ReferenceId="CopyEmailToSignInName" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
<InputClaim ClaimTypeReferenceId="ReadOnlyEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- check users password if user existed -->
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>

Related

Custom Email Verification breaks two-step Azure AD B2C Custom Sign-Up Policy

I have created a 2 step sign-up custom policy. Where the first step verifies the email, and the second step generates a new password, and it was working well.
Then I followed all the steps here:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-email-sendgrid?pivots=b2c-custom-policy
to create a custom email verifier, so I could send the verification code with custom content.
This, also seems to work fine.
However, adding it changed my flow somehow so that the email claim doesn't seem to be outputted by my first step and my claim transformation in my second step which uses the 'email' claim is failing.
In other words this works fine without the display claims portion below, and starts failing after I add it. What am I missing?
<ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!-- 2 step partner sign-up -->
<TechnicalProfile Id="PartnerSignUpVerifyEmailPage">
<DisplayName>Local Email Verification</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.signUpVerifyEmailPage</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">A user with this email address already exists.</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<!-- this breaks the next step when I add the display claims -->
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- this validation asserts the email provided isn't already in use -->
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-RaiseIfExists" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="PartnerSignUpSetNewPasswordPage">
<DisplayName>Local Email SignUp</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.signUpSetNewPasswordPage</Item>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateReadonlyEmailClaim" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
</InputClaims>
<OutputClaims>
<!--OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" /-->
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Removing PartnerClaimType="Verified.Email" from the output claim made it work.
Credit to Jas on this older post: https://github.com/azure-ad-b2c/samples/issues/193

How to update an Azure AD B2C Username sample for sending custom emails with SendGrid?

I'm using this policy for Username-based SUSI extended off of this starter pack for local account SUSI. But I had also changed that starter pack for custom verification emails with SendGrid according to the how-to found here. The requirements for login went from "just use B2C" to "login with a username and any number of users may share a verification email."
I know that TrustFrameworkExtensions_Username is extending TrustFrameworkExtensions from the local account starter pack. I can apply the same overrides from the "Local Account" ClaimsProvider over the LocalAccountSignUpWithLogonEmail and LocalAccountDiscoveryUsingEmailAddress TechnicalProfiles to the "Local Account SignIn" ClaimsProvider's LocalAccountSignUpWithLogonName and LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress TechnicalProfiles directly.
The policy inheritance looks like this:
LocalAccounts starter pack TrustFrameworkBase
|
v
LocalAccounts starter pack TrustFrameworkLocalization
|
v
LocalAccounts starter pack TrustFrameworkExtensions <- modified for custom emails via SendGrid
|
v
username-signup-or-signin sample TrustFrameworkExtenions_Username
| |
v v
username-signup-or-signin sample SignupOrSignin_Username and PasswordReset_Username
The display controls in local extensions from the how-to:
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp" />
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
Tech profiles referenced from the display controls:
<ClaimsProvider>
<DisplayName>One time password technical profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="GenerateOtp">
<DisplayName>Generate one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">GenerateCode</Item>
<Item Key="CodeExpirationInSeconds">1200</Item>
<Item Key="CodeLength">6</Item>
<Item Key="CharacterSet">0-9</Item>
<Item Key="ReuseSameCode">true</Item>
<Item Key="NumRetryAttempts">5</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="VerifyOtp">
<DisplayName>Verify one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>RestfulProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SendOtp">
<DisplayName>Use SendGrid's email API to send the code the 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://api.sendgrid.com/v3/mail/send</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">emailRequestBody</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BearerAuthenticationToken" StorageReferenceId="B2C_1A_SendGridSecret" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateEmailRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailRequestBody" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The SUSI tech profile overrides in the local extensions:
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
<DisplayClaim ClaimTypeReferenceId="givenName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="surName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I can get the sign-up part of the journey working in this manner - I could take the overrides in the extension changes found in the how-to here and add the DisplayClaims to the LocalAccountSignUpWithLogonName TechnicalProfile in
TrustFrameworkExtensions_Username.xml:
<TechnicalProfile Id="LocalAccountSignUpWithLogonName">
<DisplayName>User ID signup</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.localaccountsignup</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="signInName" Required="true" />
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" Required="true" />
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="email" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonName" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
From the OutputClaim with ClaimTypeReferenceId=email PartnerClaimType="Verified.Email" was removed. Not necessary for getting the display control actually used but for later use of the email claim in the claim bag in a subsequent orchestration step (was not set otherwise).
But when updating the TechnicalProfile for LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress in a similar way:
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<DisplayName>Reset password using username</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.localaccountpasswordchange1.1</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided user ID.</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
<Item Key="setting.retryLimit">3</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<!-- using this requires removal of OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true", below
according to https://stackoverflow.com/a/73709880/2347078 -->
<!-- line is required for non-customized email journey presently -->
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<!-- <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" /> -->
<OutputClaim ClaimTypeReferenceId="email" Required="true" />
<OutputClaim ClaimTypeReferenceId="emails" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="sub" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
In this case, see the comments as to why the OutputClaim with ClaimTypeReferenceId="email" was modified, which is referencing Jas Suri - MSFT below. With the OutputClaim looking like <OutputClaim ClaimTypeReferenceId="email" Required="true" /> the display claim is ignored entirely (the built-in verification control is presented).
With the OutputClaim entirely removed, instead the password reset policy winds up not having any fields:
All starter packs/samples are from current repos, and are using v2 pages supporting display claims.
Remove
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />

Get singin email from Azure AD in a B2C custom policy

In AAD B2C I have users created with 2 identities. I used graph to create them with this body :
{
"displayName": "John Doe",
"mail":"johndoe19287456#gmail.com",
"identities": [
{
"signInType": "userName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "606198"
},
{
"signInType": "emailAddress",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "johndoe19287456#gmail.com"
}
],
"passwordProfile" : {
"password": "Soleil!23",
"forceChangePasswordNextSignIn": false
},
"passwordPolicies": "DisablePasswordExpiration"
}
This allow the user to connect either with an email (johndoe19287456#gmail.com) or an ID (606198).
When a user input his ID and then click on the "Forgot password?" link, I'd like to get the email value from AAD so the user cannot input whatever he wants. But I'd still like it to be "verified" by sending a code to that email address. I have 2 problems :
I can't fnd a way to get the email value from AzureActiveDirectoryProvider
I can't find a way to populate the Verified.Email field (and make it readonly).
Here's a sample of one of the many things I've tried yet.
Building blocks custom claims :
<ClaimType Id="ReadOnlyEmail">
<DisplayName>Verified Email Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="emailFromAAD">
<DisplayName>Email from AAD</DisplayName>
<DataType>string</DataType>
<UserHelpText />
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="readOnlySignInName">
<DisplayName>Sign in name</DisplayName>
<DataType>string</DataType>
<UserHelpText />
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="emailValue">
<DisplayName>Matched mail</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="isEmailBoolean">
<DisplayName>is Email</DisplayName>
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="strongAuthenticationEmailAddress">
<DisplayName>string</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Email address that the user can use for strong authentication.</AdminHelpText>
<UserHelpText>Email address to use for strong authentication.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
Claims transformation :
<ClaimsTransformation Id="CopySignInNameFromReadOnly" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!-- If signin name match the regex, it is an email identifier. Oterwise, it'll be considered as username -->
<ClaimsTransformation Id="isEmail" TransformationMethod="setClaimsIfRegexMatch">
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="claimToMatch" />
</InputClaims>
<InputParameters>
<InputParameter Id="matchTo" DataType="string" Value="[^#]+#[^\.]+\..+" />
<InputParameter Id="outputClaimIfMatched" DataType="string" Value="isEmail" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailValue" TransformationClaimType="outputClaim" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" TransformationClaimType="regexCompareResultClaim" />
</OutputClaims>
</ClaimsTransformation>
Technical profiles:
<!-- Password reset step 1b - Included in step SelfAsserted-LocalAccountLookup-Combined-PwdReset
That's where the input claim is define. signInName = the Username field on the screen -->
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-SignUp">
<DisplayName>Local Account Sign Up</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>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopySignInNameFromReadOnly" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<!-- Password reset step 1a. Includes SelfAsserted-LocalAccountLookup-Combined-SignUp
Input claim is defined in this. Here, we define output claims -->
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
<DisplayName>Reset password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.showCancelButton">false</Item>
<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>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<!-- Password reset step 1c. Verify signin name on the "Continue" button clicked in the first screen -->
<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<!-- Passwod reset step 2. Only if sign in data was a username -->
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<DisplayName>Reset password using username</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
<Item Key="setting.retryLimit">5</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" DefaultValue="strongAuthenticationEmailAddress" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
For now, I get the first screen with the readonly ID working.
The next screen presents the textboxes for email. The Email from AAD shouldn't visible.
But anyway it is empty, showing that it didn't get anything from AAD or I failed to properly store it in the claim bag or pass it down. Note that I have tried getting the value both from "strongAuthenticationEmailAddress" and "signInNames.emailAddress" based on Microsoft documentation but none of it works. Maybe it's in the way I define my output claim with PartnerClaimType in the AAD-UserReadUsingIdentifier profile?
To make it clear for everyone, here's what I'd like to have. A simple page with 2 reaonly fields with a button to send the code and then another one to continue after the code has been verified.
Can anyone help me with this one?
I started from the B2C custom policies starter pack and added customization from this community repo.
UPDATE
Here is what I get from MS Graph when querying for my user :
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(identities,id,displayName,mail,surname,userPrincipalName,extension_06a19ccd80a0430c9730c62e4d96c895_ClientID,extension_06a19ccd80a0430c9730c62e4d96c895_requiresMigration)/$entity",
"id": "051e***************",
"displayName": "test - Tests2",
"mail": "johndoe19287456#gmail.com",
"surname": null,
"userPrincipalName": "051ea****************#mytenant.onmicrosoft.com",
"identities": [
{
"signInType": "emailAddress",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "johndoe19287456#gmail.com"
},
{
"signInType": "userName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "606198"
},
{
"signInType": "userPrincipalName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "051ea****************#mytenant.onmicrosoft.com"
}
]
}
You must provide the emailFromAAD as an input claim in LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress for it to be pre populated here.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/self-asserted-technical-profile#input-claims
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
<InputClaim ClaimTypeReferenceId="emailFromAAD" />
</InputClaims>
Now it will be prepopulated on this page.
If you want to force verification of this email, copy it into a read only claim using a claim transform, then do:
<OutputClaim ClaimTypeReferenceId="readOnlyEmailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
I managed to get email address from AAD by following Jas Suri - MSFT advice with a few other things. So just to sum things up here's what I did. I modified the Validation TP AAD-UserReadUsingIdentifier by replacing the output claim emailFromAAD simply by signInNames.emailAddress (no more PartnerClaimType). In the LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress TP, I added the InputClaim emailFromAAD that I populate with a CopyClaim input claims transformation. I also cleaned up the first TP of the journey SelfAsserted-LocalAccountLookup-Combined-PwdReset :
<ClaimsTransformation Id="CopySignInEmailAddressToEmail" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailFromAAD" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
<DisplayName>Reset password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.showCancelButton">false</Item>
<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>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
<ValidationTechnicalProfile ReferenceId="regexAnalysisReadOnlyUsername" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<DisplayName>Reset password using username</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
<Item Key="setting.retryLimit">5</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopySignInEmailAddressToEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" />
<InputClaim ClaimTypeReferenceId="emailFromAAD" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

How to retrieve user's consented version from ad b2c?

I am using AZure Ad B2C tenant and i have custom Consent field on signup. This is working fine and whenever we update consent its prompting to user.
I have to keep history of user's consent. Lets say on signup user1 accepted Consent (v1) and some time later user1 accepted Consent (v2), etc.
I need the history of all consents accepted/declined by user. Is that possible to retrieve?
I have tried the following.
Added two extension claim along with consent field with string type (since stringcollection is not supported for extension claims)
<ClaimType Id="extension_TermsOfUseConsented">
<DisplayName>Terms of Use Consented</DisplayName>
<DataType>string</DataType>
<UserInputType>CheckboxMultiSelect</UserInputType>
<Restriction>
<Enumeration Text="I am agreeing to the terms of use" Value="2018-10-29" SelectByDefault="false" />
</Restriction>
</ClaimType>
<ClaimType Id="extension_TempConsent">
<DisplayName>temp Consent</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="extension_ConsentHistory">
<DisplayName>consent history</DisplayName>
<DataType>string</DataType>
</ClaimType>
Added the above field in signup relyingparty section.
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn-withConsent" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />
<OutputClaim ClaimTypeReferenceId="extension_ConsentHistory" />
<OutputClaim ClaimTypeReferenceId="extension_TempConsent" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
In user journey step 3, reading the above claim in "AAD-UserReadUsingObjectId" technical profile.
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />
<OutputClaim ClaimTypeReferenceId="extension_TempConsent" />
<OutputClaim ClaimTypeReferenceId="extension_ConsentHistory" />
<OutputClaim ClaimTypeReferenceId="extension_ReadStringFromVP" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
In step 4, reading consent value and if terms is new then prompting user to accept. In claims exchange calling below technical profile.
<TechnicalProfile Id="SelfAsserted-Consent">
<DisplayName>User Consent</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.consent</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" Required="true" />
<OutputClaim ClaimTypeReferenceId="extension_TempConsent" DefaultValue="," />
<OutputClaim ClaimTypeReferenceId="extension_ConsentHistory" DefaultValue="," />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AppendConsent" />
<OutputClaimsTransformation ReferenceId="TakeConsentBackup" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-WriteUserConsentByObjectId-ThrowIfNotExists" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
In ClaimsTransformation doing some string manupulation
<ClaimsTransformation Id="TakeConsentBackup" TransformationMethod="FormatStringMultipleClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_ConsentHistory" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="extension_TempConsent" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{1},{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_ConsentHistory" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="AppendConsent" TransformationMethod="FormatStringMultipleClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="extension_TempConsent" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{1}{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_TempConsent" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
In AAD-WriteUserConsentByObjectId-ThrowIfNotExists writing the extension claim as below.
<TechnicalProfile Id="AAD-WriteUserConsentByObjectId-ThrowIfNotExists">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />
<PersistedClaim ClaimTypeReferenceId="extension_ConsentHistory" />
On claim return, its showing string manipulated values (on time on consent)
but on next login (without consent) its not returning the extension field. getting only extension_TermsOfUseConsented field value.
enter image description here
The expected claim extension manipulated in policy only not from user input. Is that a problem?
what i am missing here?
thanks in advance.

Sign-up policy - Set user attributes through code

I want to programatically set user attributes for the sign up policy. I saw a previous question (Pass parameters to Sign-up policy) asked over a year ago and it was not possible at the time. Any update on this?
Is this possible with the AuthenticationProperties.Dictionary property? Something like this?
HttpContext.GetOwinContext().Set("Policy", Startup.SignUpPolicyId);
var authenticationProperties = new AuthenticationProperties();
authenticationProperties.Dictionary.Add("myattribute", "myvalue");
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
This can be implemented using a custom policy.
A working sample of passing an input claim from a relying party application to a custom policy (e.g. an invitation flow as a sign-up policy) is here.
In the WingTipGamesWebApplication project, the InvitationController controller class has two action methods, Create and Redeem.
The Create action method sends a signed redemption link to the email address for the invited user. This redemption link contains this email address.
The Redeem action method handles the redemption link. It passes the email address, as the verified_email claim in a JWT that is signed with the client secret of the Wingtip Games application (see the CreateSelfIssuedToken method in the Startup class in the WingTipGamesWebApplication project), from the redemption link to the Invitation policy.
The Invitation policy can be found at here.
The Invitation policy declares the verified_email claim as an input claim:
<RelyingParty>
<DefaultUserJourney ReferenceId="Invitation" />
<TechnicalProfile Id="Invitation">
<InputTokenFormat>JWT</InputTokenFormat>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="WingTipGamesClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
</TechnicalProfile>
</RelyingParty>
The extension_verifiedEmail claim type, which is declared as a read-only field (so that it can't be modified by the end user), is mapped to the verified_email input claim:
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="extension_VerifiedEmail">
<DisplayName>Verified Email</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="verified_email" />
<Protocol Name="OpenIdConnect" PartnerClaimType="verified_email" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.wingtipb2c.net/identity/claims/verifiedemail" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
The Invitation user journey can be found in here.
The second orchestration step of the Invitation user journey executes the LocalAccount-Registration-VerifiedEmail technical profile:
<UserJourney Id="Invitation">
<OrchestrationSteps>
...
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
...
<ClaimsExchange Id="LocalAccountRegistrationExchange" TechnicalProfileReferenceId="LocalAccount-Registration-VerifiedEmail" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</UserJourney>
The LocalAccount-Registration-VerifiedEmail technical profile copies from the extension_verifiedEmail claim to the email claim and then displays the sign-up form with the verified email address (the extension_verifiedEmail claim):
<TechnicalProfile Id="LocalAccount-Registration-VerifiedEmail">
<DisplayName>WingTip Account</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.localaccount.registration</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateEmailFromVerifiedEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_VerifiedEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="displayName" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="sub" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExists" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SSOSession-AzureActiveDirectory" />
</TechnicalProfile>
This LocalAccount-Registration-VerifiedEmail technical profile references the AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExists validation technical profile that saves the local account with the verified email address (the email claim):
<TechnicalProfile Id="AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExists">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="displayName" />
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" />
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password" />
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="verified.strongAuthenticationPhoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateSubject" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AzureActiveDirectoryStore-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SSOSession-AzureActiveDirectory" />
</TechnicalProfile>

Resources