When using Azure AD B2C (Business to Consumer) you can easily do that with custom policies from the Identity Experience Framework.
The described solution is based on the LocalAccount templates from the Custom Policies Starter Pack GitHub repository.
Beside editing your policy with the steps below, you can download the complete files from my GitHub repository: B2C-custom-policy-with-consent
What it does:
What do I need to prepare:
- Start by downloading the current Custom Policies Starter Pack from the GitHub repo.
- Make sure you implemented the steps required to use custom policies here.
- Enter your tenant name to the header of all the files in LocalAccount template files
- Make sure you registered an application for usage with Azure B2C.
(If you don’t have your own app, try to implement this sample app).
How to implement:
First of all we create the required custom attribute, because I decided not to use my own extension app, I will use the default “b2c-extensions-app. Do not modify. Used by AADB2C for storing user data.” app with my custom policy.
Attributes for the build in policies are also stored here.
Create the needed attribute
Go to the Azure Portal (https://portal.azure.com) switch to your B2C tenant and create the following custom attribute from the B2C management blade:
- Name: TermsOfUseConsented
- Type: String
Now let’s catch up that attribute in our custom policy. Edit TrustFrameworkBase.xml and add the following ClaimType to the SECTION III of the ClaimsSchema block.
Add the additional consent page
Let’s create the additional page to present the consent screen in the user’s journey:
In the TrustFrameworkBase.xml add the following content definition to the ContentDefinitions block:
Tell the policy where extension attributes are located
Read the stored consent attribute from the directory
Locate the TechnicalProfile Id=”AAD-UserReadUsingEmailAddress” and add an additional output claim:
|<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />|
|<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />|
Create Technical Profile to write the consent attribute to AAD
|<InputClaim ClaimTypeReferenceId="objectId" Required="true" />|
|<PersistedClaim ClaimTypeReferenceId="objectId" />|
|<PersistedClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />|
|<IncludeTechnicalProfile ReferenceId="AAD-Common" />|
|<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=22.214.171.124, Culture=neutral, PublicKeyToken=null" />|
|<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />|
|<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" Required="true" />|
|<ValidationTechnicalProfile ReferenceId="AAD-WriteUserConsentByObjectId-ThrowIfNotExists" />|
Create the custom user journey
|<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">|
|<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />|
|<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />|
|<OrchestrationStep Order="2" Type="ClaimsExchange">|
|<Precondition Type="ClaimsExist" ExecuteActionsIf="true">|
|<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />|
|<!-- This step reads any user attributes that we may not have received when in the token. -->|
|<OrchestrationStep Order="3" Type="ClaimsExchange">|
|<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />|
|<!-- execute consent step if the users consented version does not met the current version,|
|otherwhise this step is skipped -->|
|<OrchestrationStep Order="4" Type="ClaimsExchange">|
|<Precondition Type="ClaimEquals" ExecuteActionsIf="true">|
|<ClaimsExchange Id="SelfAssertedConsentExchange" TechnicalProfileReferenceId="SelfAsserted-Consent" />|
|<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />|
|<ClientDefinition ReferenceId="DefaultWeb" />|
This is a copy of the default user journey from the TrustFrameworkBase.xml file with the added consent page in the 2nd last step (Order=”4″)
Active the new journey as the default user journey
That’s it, you can now load all policies into your B2C tenant and give them a try. Don’t forget you need to upload them in the following order:
- <additional files>
Since the custom consent page did not show the consent text itself you should put this into the UI customization HTML file and reference it the custom policy.
You can do that by modify the parameter LoadUri of the ContentDefinition Id=”api.selfasserted.consent”
This article was incredibly helpful. Thank you!
I got this to work with the local accounts but I also tried setting this up with the SocialAndLocalAccounts, however I kept getting this error.
Unable to upload policy. Reason : Validation failed: 1 validation error(s) found in policy “B2C_1A_SIGNUP_SIGNIN” of tenant “meddevapib2c.onmicrosoft.com”.Claim type “identityProvider” is the output claim of the relying party’s technical profile, but it is not an output claim in any of the steps of user journey “SignUpOrSignIn-withConsent”.
Its just like the message states, you have an outputClaim in your signupsign policy but that claim is missing in one or more of the technical profiles that are called by your user journey.
Hi this article was very useful. Can you please let me know how to include hyperlink in terms and agreement and also i would like to display termsofconstent custom attribute in signup page instead of redirection to new page.
Hi, I only implemented a simple solution here, which only display the Radio control to accept the terms.
The tems of use content itself is a seperate HTML page (custom UI)
Very helpful article! i have a similar requirement with slight variation, Instead of consent i would like to add a custom user name.as a custom attribute. Up on successful login if user does not have user name in AD then redirect to self asserted page where use will have to select the username.
Now at this time i would like to put validation on the username field. He should not be able to choose something which is already taken, Also i would like to make exception for few reserved words which can not be chosen as user name.
Why not put that scenario into the registration screen instead of a custom page and mark that custom attribute as required ?
Or is it for a solution where users are already present and should add their new “username”
It is when users are already present and later they are coming to choose their username for additional functionality on website.
Hi, wonderful article, but I’m trying to reproduce this flow using Local and Social authentication methods whit any progress, using basic Azure examples template from https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/SocialAndLocalAccounts. Can you help me with this? Please
it should not be that different from the Local Account scenario, because the SocialandLocal account sample ist based on the LocalAccountOnly sample from Microsoft. Beside implementing the technical profiles and claims you need to put the parts into the correct steps in your user journey
Hi, Thanks for the article. Its working perfectly. I have an additional requirement. Is that possible to store all consented versions (like user1 accepted v1, user1 accepted v2) and return that in any claim?
Hi, could be a bit tricky as over the time when you reach V50 the claim can become very long. It could be better to use the Rest API Claims Provider and create some kind of TOS history stored in a database. Of course technically it is possible to store all versions in an string attribute by adding the new consentet version to the old one but I would use a second attribute for that.
This article helped a-lot, I explored too many documentations about the Custom Policies but wasn’t able to create consent page and modified User Journey, This blog made it quite easy for me to integrate pages accords to my need. I Just need one more help here,
Actually for users who has accepted Terms consent the profile section still shows that value as undefined under consent and Major section on users profile page. Could You please help me with that how can we store that accepted consent value.
the consent is stored on the users attribute/claim extension_TermsOfUseConsented so you should be able to read that by modify the AAD-UserReadUsingObjectId. You can verify the correct stores by for example read useres extension attributes with PowerShell
Thanks for this article
I’m running through your instructions and I’m getting an error trying to upload the SignUpSignIn policy. It says
“An error occurred while creating extension property “TermsOfUseConsented” in tenant “”. Error returned was 409/Request_MultipleObjectsWithSameKeyValue: An extension property exists with the name extension__TermsOfUseConsented.”
When I delete that extension property the policy uploads successfully. However when trying to use the policy I see the following error message in Application Insights:
“The following extension properties are not available: extension__TermsOfUseConsented”
When I add the extension property in I then get a message similar to the first one, saying that the property already exists.
Can you advise please?
Thanks for making this. Huge help in plain English.
this is a really nice docu it worked for me with the first trial, thanks a lot.
However, what is not working is the “skip” step in the UserJourney.
I always get the claim “extension_TermsOfUseConsented”: “2022-05-18” within my token and it matches the Value I compare in the UserJourney. But this step never gets skipped.
So I expect the claim is not properly persisted during AAD-WriteUserConsentByObjectId-ThrowIfNotExists
I am missing to read the claim when reading the Object from the AAD
You can check on the user object via Graph API that the attribute is set or not. I agree to your expectation that it could also be an issues on reading the object, so you might want to implement some debugging with application insights.
You might also want to check another existing example of a consent implementation: https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-sign-up-versioned-tou
Thank you for this guide!
I am running into a couple of problems (note I had to do some tweaks to get it to work with social accounts):
1) the consent doesn’t seem to be persisted as I have to consent on every login
2) the consent isn’t returned to the calling application
Any hints what I might have done wrong?
This is a very old post, but I remember it worked in the past as I imlemented this at some customers.
The solution was never designed to send consent information to the application in claims.
There is a very good, even better as my old post, solution in the GitHub samples of B2C.
I even preferr that example now over mine.
I got the returning of the consent working but still unable to persist the consent in AD. I looked at that example but found it harder to wrap my head around. I also preferred having the consent as a separate page. I think it is useful that they store the date/time of the consent. I guess I will give it a try to see if I can get that working and if so if I can then move it to its own page.