I am trying to collect user details using LocalAccountSignUpWithLogonName custom policy.I have added <OutputClaim ClaimTypeReferenceId="email" />
to LocalAccountSignUpWithLogonName as outputclaim. I want to make the email field optional, but if user enter the email I want to enable the restrictions.Below is my email claim
<ClaimType Id="email">
<DisplayName>Your Email Address</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="email" />
</DefaultPartnerClaimTypes>
<UserHelpText>Email address that can be used to contact you.</UserHelpText>
<UserInputType>TextBox</UserInputType>
<Restriction>
<Pattern RegularExpression="^[a-zA-Z0-9.+!#$%&'^_{}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" HelpText="Please enter a valid email address." />
</Restriction>
But when I add the pattern restriction to claim, its making the field mandatory.
I achieved it by changing the regular expression from RegularExpression="^[a-zA-Z0-9.+!#$%&'^_{}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" to RegularExpression="^$|^[a-zA-Z0-9.+!#$%&'^_{}~-]+#[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$" `.
I had to prefix the expression with ^$| which accept blank/empty or the actual email.
Related
what is maximum character length of email in Azure Custom ADB2C policy
this is my claim type
<ClaimType Id="signInName">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<UserHelpText />
<UserInputType>TextBox</UserInputType>
<Restriction>
<Pattern RegularExpression="^[a-zA-Z0-9.+!#$%&'^_`{}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" HelpText="Please enter a valid email address." />
</Restriction>
</ClaimType>
As long as it's a valid email it should work (ie charachters#domain.com).
B2C doesn't have limitation on valid email address.
Scenario
In my sign up flow, I have the requirement to show:
an email address field and a confirm email address field
a password field and a confirm password field
I have to validate that:
the text entered in the email address field matches the text entered in the confirm email address field
the text entered in the password field matches the text entered in the confirm password field
the email address entered for a user doesn't already exist in Azure B2C
These validations should display in the existing form, rather than a new page.
The only way I've been able to display validation messages inline as a result of the output from Validation Technical Profiles, is by using the two built in asserts, AssertStringClaimsAreEqual (for the email and password comparisons) and AssertBooleanClaimIsEqualToValue (for the existing user check). These cause messages, defined in the calling Claims Provider Technical Profile, to be displayed when the assert fails.
Problem
I have three error cases that require distinct messages, and I'm only able to define and display messages for two. Currently, I'm working around this by displaying "Email and confirm email, or password and confirm password do not match." for both the failed text comparisons, and "This email address is already in use, please sign in." for the existing user case. The existing user case is fine, but I would describe the single text comparison message as 'Okay, but doesn't really meet the acceptance criteria' and 'I can't believe there's not a better way to do this'.
Any help would be gratefully received at this point.
Current Code Snippets
The relevant Validation Technical Profiles:
<TechnicalProfile Id="EmailMatchValidator">
<DisplayName>Check if email and confirm email match</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="DoEmailsMatch"/>
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailsMatch" Required="true"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="PasswordMatchValidator">
<DisplayName>Check if password and confirm password match</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="DoPasswordsMatch"/>
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="passwordsMatch" Required="true"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="UserExistsValidator">
<DisplayName>Asset if user exists</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userExists"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userExists" Required="true"/>
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="DoesUserExist"/>
</OutputClaimsTransformations>
</TechnicalProfile>
These Technical Profiles reference Output Claims Transformations, which look like this:
<ClaimsTransformation Id="DoEmailsMatch" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim1"/>
<InputClaim ClaimTypeReferenceId="confirmEmail" TransformationClaimType="inputClaim2"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase"/>
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="DoPasswordsMatch" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim1"/>
<InputClaim ClaimTypeReferenceId="confirmpassword" TransformationClaimType="inputClaim2"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase"/>
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="DoesUserExist" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="userExists" TransformationClaimType="inputClaim"/>
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false"/>
</InputParameters>
</ClaimsTransformation>
DoEmailsMatch and DoPasswordsMatch are straightforward enough, using the AssertStringClaimsAreEqual built-in assert to display a message defined in the Metadata of the calling Claim Provider Technical Profile:
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">Email and confirm email, or password and confirm password do not match.</Item>
DoesUserExist relies on AssertBooleanClaimIsEqualToValue, which means it needs a boolean to work with, to decide whether to display the other message defined in the Metadata:
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">This email address is already in use, please sign in.</Item>
The code that sets that boolean is probably out of scope for this question, but in short, it gets set by a read on the AAD database that returns a matching User Principal Name in a claim, or no claim at all. This undergoes a Claim Transformation using DoesClaimExist to set the boolean userExists claim, used by DoesUserExist above.
For the third one, do an AAD Read operation instead, and it has a built in error message for when the use exists.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#ui-elements
You can find this used in the AAD B2C starter pack for the default sign up flow.
For password matching, use password and reenter password, use the built in way
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/LocalAccounts/TrustFrameworkBase.xml#L648
Now you should only need the 1 claims transform for email matching.
I'm using ADFS as an IdP for Azure B2C through OpenID Connect.
Login works and B2C sends UPN from ADFS as socialIdpUserId claim in JWT token.
But group claims from ADFS do not work.
How to receive group claims in JWT?
Here is the setup:
ADFS claim rule: domain security groups and upn
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname",
Issuer == "AD AUTHORITY"] =>
issue(store = "Active Directory",
types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", "http://schemas.xmlsoap.org/claims/Group"),
query = ";userPrincipalName,tokenGroups(longDomainQualifiedName);{0}",
param = c.Value);
Client permissions are set to openid and allatclaims
New group claim type definition in TrustFrameworkBase policy in ClaimsSchema:
<ClaimsSchema><ClaimType Id="group">
<DisplayName>group</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="group" />
<Protocol Name="OpenIdConnect" PartnerClaimType="group" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/claims/Group" />
</DefaultPartnerClaimTypes>
</ClaimType></ClaimsSchema>
Output group claim definition in TechnicalProfile in TrustFrameworkExtensions policy:
<OutputTokenFormat>JWT</OutputTokenFormat><OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="UPN" />
<OutputClaim ClaimTypeReferenceId="group" PartnerClaimType="group" />
</OutputClaims>
Output group claim definition in TechnicalProfile in SignUpOrSignIn policy file
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" />
<OutputClaim ClaimTypeReferenceId="group" />
<OutputClaim ClaimTypeReferenceId="authmethod" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
But there is no group claim comes with JWT token! Why?
Here is how to issue group claims out of B2C:
1. Define a new claim type in for groups in the Base policy file. This definition should be at the end of < ClaimsSchema > element (yes, the man who wrote about stringCollection was write!)
<ClaimType Id="IdpUserGroups">
<DisplayName>Security groups</DisplayName>
<DataType>stringCollection</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="groups" />
<Protocol Name="OpenIdConnect" PartnerClaimType="groups" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/claims/Group" />
</DefaultPartnerClaimTypes>
</ClaimType>
Use this new defined claim in the < OutputClaims > in the extenstion policy in < ClaimsProvider > definition for ADFS
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" />
<OutputClaim ClaimTypeReferenceId="IdpUserGroups" PartnerClaimType="http://schemas.xmlsoap.org/claims/Group" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="SAML fmdadfs4.local"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="SAML ADFS4 fmdadfs4.local" />
</OutputClaims>
Use the same claim in the < OutputClaims > dfinition in relyng party definition under < RelyngParty > elemnt in your SignIn policy file
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" />
<OutputClaim ClaimTypeReferenceId="IdpUserGroups" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="userPrincipalName" />
Issue group claims from ADFS as it is shown here
Looks like OP simply has the partnerclaimtype misspelled.
Not certain because you may have mapped something non-standard, but I'm thinking you just need to change your PartnerClaimType from group to groups.
<ClaimType Id="groups">
<DisplayName>Groups</DisplayName>
<DataType>stringCollection</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="groups" />
</DefaultPartnerClaimTypes>
<UserHelpText>List of group memberships</UserHelpText>
</ClaimType>
Once you define the ClaimType, you don't need to specify the PartnerClaimType anywhere else - unless you're overriding the value.
I'd also consider using the DefaultValue="" attribute so you can check your policy is properly executing the output claim.
OutputClaim ClaimTypeReferenceId="groups" DefaultValue="no groups assigned
I've been using the built-in SignUpOrSignIn policy for a while, but I'm now moving to a custom policy.
When I set up the built-in policy, I was able to choose from a list of built-in application claims (like displayName and jobTitle), and select which ones I wanted to be returned in the token when the user signed in.
Now I'm setting up the custom policy I want to do the same thing, but I can't get it to work.
So far, in TrustFrameworkBase I have added a ClaimType of jobTitle:
<ClaimType Id="jobTitle">
<DisplayName>Job Title</DisplayName>
<DataType>string</DataType>
<UserHelpText>Job title.</UserHelpText>
</ClaimType>
I've added the following OutputClaim to the TechnicalProfile with ID login-NonInteractive:
<OutputClaim ClaimTypeReferenceId="jobTitle" PartnerClaimType="jobTitle" />
And I've added the following OutputClaim to the TechnicalProfile with ID SelfAsserted-LocalAccountSignin-Email:
<OutputClaim ClaimTypeReferenceId="jobTitle" />
But the jobTitle claim doesn't come through with the others in the token. I've done the same for given_name and that does work. If I change the first OutputClaim to:
<OutputClaim ClaimTypeReferenceId="jobTitle" PartnerClaimType="given_name" />
then a jobTitle claim does come through, but with the value of the given_name claim. This implies I'm just using the wrong PartnerClaimType but there doesn't seem to be a list of them anywhere.
How can I get the built-in job title attribute to be returned as a claim in the token when the user signs in using their local B2C account?
If you only want to read the jobTitle claim (or other claims) for the user and then issue it (or them) in the token, then you must:
1) Declare the jobTitle claim:
<ClaimType Id="jobTitle">
<DisplayName>Job Title</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="job_title" />
<Protocol Name="OpenIdConnect" PartnerClaimType="job_title" />
</DefaultPartnerClaimTypes>
</ClaimType>
2) Add the jobTitle claim as an output claim to the AAD-UserReadUsingObjectId technical profile:
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="jobTitle" />
</OutputClaims>
...
</TechnicalProfile>
3) Add the jobTitle claim as an output claim to the relying party technical profile:
<RelyingParty>
...
<TechnicalProfile Id="PolicyProfile">
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="jobTitle" />
</OutputClaims>
...
</TechnicalProfile>
</RelyingParty>
When I try to execute my sign-up policy I'm receiving:
Why?
I was trying to display the field but I hadn't defined the <UserInputType> node.
<ClaimType Id="extension_HelloWorld">
<DisplayName>Hello World</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="HelloWorld" />
<Protocol Name="OpenIdConnect" PartnerClaimType="HelloWorld" />
</DefaultPartnerClaimTypes>
<!-- I had the following line commented out -->
<!--<UserInputType>Readonly</UserInputType>-->
</ClaimType>