How do I add a user attribute in a SAML SSO response - azure-ad-b2c

I am working on using Azure AD B2C as the Identity Provider for a custom site that also provides SSO for a Blackboard Learn LMS site. Blackboard can do SSO through SAML, so I am using those capabilities within B2C. Unfortunately, SAML is not a protocol I am well-acquainted with, so my ability to troubleshoot is limited. I am working off of the tutorial at:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/saml-service-provider?tabs=windows&pivots=b2c-custom-policy
Everything is going mostly pretty well, but I am trying to map user attributes that are shared with Blackboard so that when a new user is created, they have at least a basic profile. There are a couple attributes that are coming through naturally in this tutorial, but I am trying to specify the username for the profile (specs require that it is the email address), and I am not having any luck.
I added the OutputClaim named "userPrincipalName" in the SignUpOrSignIn policy (see code below), as it has the appropriate value coming from B2C, but according to a Blackboard test page, it is not among the values that are being sent in the SAML response (see screenshot further down).
...
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="SAML2"/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="email" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="objectId"/>
</OutputClaims>
<SubjectNamingInfo ClaimType="objectId" ExcludeAsClaim="true"/>
</TechnicalProfile>
</RelyingParty>
...
Are there further steps that need to happen? I have tinkered with the TrustFrameworkExtensions (and Base) here an there, as well, but to no avail, and I can't find any described process for adding new attributes to the SAML response. Does anyone have an example/tutorial that could walk me through it?
(I looked at this question and an answer indicates that I might need to change the NameIdFormat. I can see that the format in my B2C app metadata is incorrect as it suggests, but I'm not sure how I would go about changing it)
Update
Based on #JasSuri-MSFT's suggestion, I set a (non-empty) default value for the UPN, and sure enough, that default value came through. So it would seem that the problem is that the UPN is null.
This is strange, as in the edit page for the user in B2C, there is clearly a UPN value. It also highlights another oddity that I had not mentioned. But there are other OutputClaims listed in that policy that are not being included either. One of them, email, I explicitly provided a value for, and it is apparently presenting as null as well.
Clearly there is another piece in the plumbing somewhere that I don't have established.

Add signInNames.emailAddress as an output claim in AAD-UserReadUsingObjectId and add signInNames.emailAddress as an output claim in the RelyingParty section. Use a partnerClaimType in the output claim in the RelyingParty to issue the claim with the name you like into the SAML Response.
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email"/>
https://github.com/azure-ad-b2c/azureadb2ccommunity.io/wiki/LocalAccount-Sign-In-and-Sign-Up-policy
https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-profile-attributes#identities-attribute
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#outputclaims

Related

How to populate Display Name with Email using B2C custom user flow

I'm implementing a local user B2C custom user flow policy based on Microsoft's guide.
My custom signup/signin policy is working but I want to modify it so that the Display Name field in B2C is automatically populated with the user's email address.
Currently I've disabled removed the Display Name claim from the signup form, meaning it get's populated with "unknown".
I'm using the provided samples as the basis of my policy so if someone can provide an example of how to modify these to support automatically populating the Display Name with the users email that would be perfect.
Sounds like you already removed it as an Output Claim from the LocalAccountSignUpWithLogonEmail.
In that same step, you should be able to add an OutputClaimsTransformation with TransformationMethod="CopyClaim", the input being the "email" and the output being the "displayName".
The claims transformation will look like this:
<ClaimsTransformation Id="CopyEmailAddressToDisplayName" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
and to call it from the technical profile, add this section right below the output claims.
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopyEmailAddressToDisplayName" />
</OutputClaimsTransformations>
Reference: https://learn.microsoft.com/en-us/azure/active-directory-b2c/general-transformations
NOTE: If you're going to change these, I recommend copying the technical profile to the TrustFrameworkExtensions file to avoid confusion between what existed in the base and what you customized.

Registration with Azure AD B2C custom policy fails due to missing claim information

I am currently investigating the possibilities to send emails from a custom policy in Azure AD B2C through a custom email service provider. To do so, I was following this tutorial from the Microsoft documentation: https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-email-sendgrid.
Since we have been using custom policies for quite some time now, I tried to adopt the tutorial right away to our own custom policy: we use a multistep registration process with the email verification step being the first and the personal data step being the second step.
My efforts have been successful with connecting the email service provider, sending the code through it and proceed to the second step of the registration process after successfully verifing the email address.
However, when I want to complete the second step of the registration and have the user be actually created in AD I see the following error in the frontend:
Unable to validate the information provided.
To gain further insights I activated developer mode in the policy and checked the traces in Application Insights where I found the following error:
The claim type "signInNames.emailAddress", designated as the identifier claim type, could not be found in the claims collection for the claims principal in tenant id "B2C_1A_signup_notificationtest".
caused in the technical profile AAD-UserWriteUsingLogonEmail. However, as you can see below in that technical profile the aforementioned claim type signInNames.emailAddress is derived throught the partnerClaimType directive in the input claims of the technical profile:
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<!-- THIS IS THE LINE I AM REFERRING TO: -->
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress"
Required="true"/>
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress"/>
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown"/>
<PersistedClaim ClaimTypeReferenceId="passwordPolicies"
DefaultValue="DisablePasswordExpiration"/>
<!-- Optional claims. -->
<PersistedClaim ClaimTypeReferenceId="extension_Salutation"/>
<PersistedClaim ClaimTypeReferenceId="givenName"/>
<PersistedClaim ClaimTypeReferenceId="surname"/>
<PersistedClaim ClaimTypeReferenceId="country"/>
<PersistedClaim ClaimTypeReferenceId="extension_Company"/>
<PersistedClaim ClaimTypeReferenceId="extension_Kundennummer"/>
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated"/>
<OutputClaim ClaimTypeReferenceId="authenticationSource"
DefaultValue="localAccountAuthentication"/>
<OutputClaim ClaimTypeReferenceId="userPrincipalName"/>
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common"/>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD"/>
</TechnicalProfile>
So my question would be: where can I look to debug further? Am I looking in the right place or does the error message mean something completely different?
Also as a side note: due to the nature of the identity experience frameworks configuration, there is a lot of xml involved. Therefore I have posted the relying party policy file that also includes the relevant parts for the service connection as a gist here: https://gist.github.com/mmaedler/0a555fc3f9e6036e235a15419e7afdd5
UPDATE Additionally I have added all relevant (hopefully) Technical Profiles as well as the UserJourney itself to this new gist: https://gist.github.com/mmaedler/19ab309897f3a7d993816eb34adc7edb
Also I have used your advise to investigate further. Since I overwrite the Technical Profile LocalAccountSignUpMultiStep-1 to replace the current built in code verification with the one using our mailing backend it suggests the output of this is lacking the email address in a field/format that is expected by the following technical profile in step 2. I have added a new OutputClaim to LocalAccountSignUpMultiStep-2 with a ClaimTypeReferenceId="email" which lead to a new input appearing on registration step 2. Entering an email address there ended in a successful signup and token creation.
Misunderstanding, this XML snippet input claim translates to - "Please find me this user by searching for the email through all users' signInNames.emailAddress attribute".
The error from the log states that, when this lookup operation was performed, the value for signInNames.emailAddress was null. This means that the claim email was never output as an output claim in any previous technical profile called by an orchestration step.
Look back through all technical profiles called prior to this and determine which claim has the user email inside it. Make sure that claim is output by a technical profile that is called by an orchestration step.

Azure B2C Azure Active Dicrectory retrieve email in token using policies

I have an Azure B2C tenant I'm setting up using policies and I'm facing the following problem. One of my social providers is configured as an Azure Active Directory but, when getting the token, the email is not included.
I have other providers like Google or LinkedIn where I've been able to collect the email using these OutputClaims inside the TechnicalProfile of each ClaimProvider.
Google
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
LinkedIn
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="elements[0].handle~.emailAddress" />
I'm wondering if there is a similar approach to what I need but the Azure Active Directory provide offers an email's PartnerClaimType.
I checked the doc for this kind of provider but found nothing. I checked as well the optional claims set and the email is mentioned but got no clue how to make it work.
Thank's for your time!
Try
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="unique_name" />
https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens#claims-in-an-id_token

Unable to map assertionSubjectName when handling SAML2 response

I have created a custom TechnicalProfile using the SAML2 protocol and I use this profile in a User-Journey to log in to a 3rd party identity provider. My SAML AuthRequest works well and I receive a valid response.
This response contains a "Subject" element as shown below.
<saml:Subject>
<saml:NameID NameQualifier="REMOVED"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">REMOVED</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2018-11-29T18:02:53Z" Recipient="REMOVED" InResponseTo="_d09b9112-adea-49f2-809f-8c5fcbaa6261" />
</saml:SubjectConfirmation>
</saml:Subject>
According to documentation found here: Active Directory B2C Advanced Policies - Features 6
and this StackOverflow question, I should be able access the value of the NameID element in my OutputClaims mapping by using a Partner Claim Type of "assertionSubjectName". I have done this as shown below:
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="firstName"/>
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="lastName"/>
<OutputClaim ClaimTypeReferenceId="dob" PartnerClaimType="dateOfBirth"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailAddress"/>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="REMOVED" />
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="assertionSubjectName" />
All of the claims other than "socialIdpUserId" map correctly as they are found in the "AttributeStatement" element of the SAML Response, but the "socialIdpUserId" claim is not mapped. In fact - when tracing the User Journey via ApplicationInsights, the messaging from that step of the journey clearly indicates that the step is mapping those other claims, but the "socialIdpUserId" claim is not mentioned at all.
My question is this - has anyone actually seen the use of the "assertionSubjectName" operate successfully? Or am I reviewing out of date documentation and is there a "better" way to achieve the same end result that I am not currently aware of?

How do I read the NameID element as a claim in a B2C TechnicalProfile for a SAML2 identity provider?

I followed the example in Set up sign-in with a Salesforce SAML provider by using custom policies in Azure Active Directory B2C
and was able to successfully SSO from Salesforce into Azure B2C. However, I would also like to retrieve the value of
the NameID element from the SAML Assertion as a claim. Is this possible?
For example, say that the incoming SAML 2.0 Assertion posted to B2C's assertion consumer endpoint looks
something like this simplified XML.
<saml:Assertion>
<saml:Issuer>https://mytestinstance-dev-ed.my.salesforce.com</saml:Issuer>
<saml:Subject>
<saml:NameID>emp99999</saml:NameID>
</saml:Subject>
<saml:AuthnStatement AuthnInstant="2018-10-04T16:56:44.192Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="userId">
<saml:AttributeValue>009f90000099zzz</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="username">
<saml:AttributeValue>user000#example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email">
<saml:AttributeValue>user000#example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="">
<saml:AttributeValue>false</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
The TechnicalProfile lets you access any Attribute from the AttributeStatement as a claim by referencing its
Name in the PartnerClaimType in an OutputClaim element. For example, with the TechnicalProfile below, the socialIdpUserId claim is
set to the value of the userId attribute from the SAML Assertion, "009f90000099zzz". What I would like to have is
is a claim named employeeId that is set to "emp99999," the value of the NameID element.
<TechnicalProfile Id="salesforce">
<DisplayName>Salesforce</DisplayName>
<Description>Login with your Salesforce account</Description>
<Protocol Name="SAML2"/>
<Metadata>
<Item Key="RequestsSigned">false</Item>
<Item Key="WantsEncryptedAssertions">false</Item>
<Item Key="WantsSignedAssertions">false</Item>
<Item Key="PartnerEntity">https://mytestinstance-dev-ed.my.salesforce.com/.well-known/samlidp/TestB2C.xml</Item>
</Metadata>
<!-- <CryptographicKeys> -->
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="userId"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email"/>
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="username"/>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="SAMLIdp" />
<!-- We want the Subject/NameID value as a custom employeeId claim. URI reference doesn't work. -->
<OutputClaim ClaimTypeReferenceId="employeeId"
PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" />
</OutputClaims>
<!--<OutputClaimsTransformations> -->
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
I tried using PartnerClaimType values like "NameID", "NameIdentifier", as well as the well-known URI for NameIdentifier
and none of these seemed to work.
I also saw some references to the use of the SubjectNamingInfo element and experimented with it,
but that only seems relevant in defining the token sent to a RelyingParty
and not in reading claims from a token received from an IDP.
Also, what about any other Assertion elements? For example, depending on how an IDP uses it,
I could see a need to read the AuthnContextClassRef value to make a decision about whether
to issue an MFA challenge.
The solution to use assertionSubjectName is definitely correct. In fact, MSFT updated their main documentation page on Dec 20th 2018 to add further information: Define a SAML Technical Profile. That being said - I wanted to add one more note that may help others who come across this behavior. It appears that Azure will NOT map the NameID to your output claim if the NameID element has a "NameQualifier" attribute.
Example - this will map to your output claim:
<Subject>
<NameID>foo#bar.com</NameID>
This will NOT map to your output claim:
<Subject>
<NameID NameQualifier="https://bar.com/realms/foo">foo#bar.com</NameID>
Hopefully this will be helpful to anyone who has run into a situation where "assertionSubjectName" does not appear to work. That being said - it seems that this behavior is not all that desirable and I have reached out to MSFT to find out if this is WAD or not.
For the SAML2 protocol, the value of the NameID element can be accessed by using a PartnerClaimType with the value "assertionSubjectName".
This is mentioned in the "Specifying a technical profile for a SAML 2.0 claims provider" section of
Features part 6
in the Advanced Policies Git repo.
For example, to map the NameID from the SAML Assertion to the "employeeId" claim, set its PartnerClaimType to "assertionSubjectName"
in the OutputClaim claim element.
<OutputClaims>
<!-- Other claims -->
<OutputClaim ClaimTypeReferenceId="employeeId"
PartnerClaimType="assertionSubjectName" />
</OutputClaims>
Another example can be found in
Specifying a technical profile for a SAML 2 excerpt from documentation.docx.
Edit
Per Adam C's answer, this is now documented at
Define a SAML technical profile in an Azure Active Directory B2C custom policy. He also notes that B2C will not map NameID to your output claim if the NameID element has a "NameQualifier" attribute.
In my case, I was trying to extract the NameID from a SAML response where the NameID element had an SPNameQualifier attribute. Using assertionSubjectName was not working.
However, what did work was using the SPNameQualifier attribute value.
For example, suppose your SAML response looks like
<saml:Subject>
<saml:NameID SPNameQualifier="https://bar.com/realms/foo">emp99999</saml:NameID>
</saml:Subject>
To extract the NameID value you can set your claim mapping as
<OutputClaims>
<!-- Other claims -->
<OutputClaim ClaimTypeReferenceId="employeeId" PartnerClaimType="https://bar.com/realms/foo" />
</OutputClaims>

Resources