I try to add a reCaptcha on my signin page using custom policies. How can I add a reCaptcha field to my custom signin page that is based on the "unified.html" template? I need it to be able to validate the code inside the policy ValidationTechnicalProfile so it has to come back from the UI.
I started off with
this sample
which works with an OrchestrationStep with ContentDefinitionReferenceId="api.selfasserted" (based on a selfasserted page). In this sample, the code comes from a custom field "g-recaptcha-response-toms" so I added this claim to our code. But there is no way to have it render by the engine.
On our own policy, the first step is this one. Note that the ContentDefinitionReferenceId is "api.signuporsignin" :
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
The "api.signuporsignin" content definition points to a unifiedssp contract. And "UnifiedCustomUrl" is a blob storage url to our own cutom page, based on "unified.html" page from Microsoft templates.
<ContentDefinition Id="api.signuporsignin">
<LoadUri>{Settings:UnifiedCustomUrl}</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.5</DataUri>
<Metadata>
<Item Key="DisplayName">Signin and Signup</Item>
</Metadata>
</ContentDefinition>
Now our technical profile "SelfAsserted-LocalAccountSignin-Username" is this one :
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<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="setting.showCancelButton">False</Item>
<Item Key="setting.forgotPasswordLinkLocation">AfterInput</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The last names you provided are not the same</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" DefaultValue="{OIDC:LoginHint}" />
<InputClaim ClaimTypeReferenceId="g-recaptcha-response-toms" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="g-recaptcha-response-toms" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="errorCode" Required="true" DefaultValue="1234" />
<OutputClaim ClaimTypeReferenceId="errorMessage" Required="true" DefaultValue="Error message to return" />
<OutputClaim ClaimTypeReferenceId="isDevlopmentEnvironment" DefaultValue="{Settings:UseFakeEmailForTests}" AlwaysUseDefaultValue="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- Validates Google reCaptcha -->
<ValidationTechnicalProfile ReferenceId="login-Recaptcha" />
<!-- Initiate a normal logon against Azure AD B2C -->
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
EDITED 02/01/2023*
I finaly managed to gather th informations in my form by changing the page contract from "unifiedssp:2.1.5" to "selfasserted:2.1.9". Now there's another problem: there is no way to display the "Forgot password" link. I could do like in the sample and generate a link. But since we need to go back to our UI (for some validations before starting the actual flow), we used the old method with the error return, not the one with sub journeys. So it's a real pain in the *** just to get a simple Forgot Pasword link, we'll need to reingeneer our entire reset password flow. It's not optimal to say the least. We should have the option to display that link in the "selfasserted" page layout contrat too, juste like in the "unifiedssp".
Hi Patrice Côté ,
I'm glad that you were able to resolve your issue and thank you for posting your solution so that others experiencing the same thing can easily reference this! Since the Microsoft Q&A community has a policy that "The question author cannot accept their own answer. They can only accept answers by others ", I'll repost your solution in case you'd like to "Accept " the answer.
Issue:
You wanted to add a reCaptcha on your signin page using custom policies in Azure AD B2C. You were using the sample A B2C IEF Custom Policy which integrates with Google Captcha
You ran into two main issues:
When adding the custom field "g-recaptcha-response-toms", it would not render.
After you were able to resolve the first issue and get the information in the form, the "Forgot password" link would not display.
Solution:
Updating the the page contract from "unifiedssp:2.1.5" to "selfasserted:2.1.9" page layout version resolved the issue of not being able to gather the information on the form.
To add the "Forgot password" link, you had to create it on page load with JavaScript (see similar example with Terms of Use link):
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const login_hint = urlParams.get("login_hint");
const ui_locales = urlParams.get("ui_locales");
const state = urlParams.get("state");
const redirect_uri = urlParams.get("redirect_uri");
document.getElementById("login_hint").innerHTML = login_hint;
const forgotPassurl = ""+redirect_uri+"#error=access_denied&error_description=AADB2C90118%0d%0alogin_hint="+login_hint+"%0d%0a&state="+state+"&lang="+ui_locales+"";
if(ui_locales == "fr") {
document.getElementById('recaptchaScript').src = document.getElementById('recaptchaScript').src+"?hl=fr";
document.getElementById("forgotPassword").innerHTML = "Mot de passe oublié ?";
else {
document.getElementById('recaptchaScript').src = document.getElementById('recaptchaScript').src+"?hl=en";
document.getElementById("forgotPassword").innerHTML = "Forgot your password ?";
document.getElementById("forgotPassword").href = forgotPassurl;
If you have any other questions or run into more issues with the sample, please let me know.
Thank you again for your time and patience throughout this issue.
Please remember to "Accept the answer" if the answer accurately represents the issue and resolution, so that others in the community facing similar issues can easily find the solution.
So the EDITED part solved the problem. As for the link, we had to basicaly create it on page load with javacript. We take the domain from the request target_uri, the login_hint and tenantId from the request's query params with the same name.
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const login_hint = urlParams.get("login_hint");
const ui_locales = urlParams.get("ui_locales");
const state = urlParams.get("state");
const redirect_uri = urlParams.get("redirect_uri");
document.getElementById("login_hint").innerHTML = login_hint;
const forgotPassurl = ""+redirect_uri+"#error=access_denied&error_description=AADB2C90118%0d%0alogin_hint="+login_hint+"%0d%0a&state="+state+"&lang="+ui_locales+"";
if(ui_locales == "fr") {
document.getElementById('recaptchaScript').src = document.getElementById('recaptchaScript').src+"?hl=fr";
document.getElementById("forgotPassword").innerHTML = "Mot de passe oublié ?";
else {
document.getElementById('recaptchaScript').src = document.getElementById('recaptchaScript').src+"?hl=en";
document.getElementById("forgotPassword").innerHTML = "Forgot your password ?";
document.getElementById("forgotPassword").href = forgotPassurl;
Hello Patrice Cote, I noticed the same problem with the content definition = api.signupsignin the captcha functionality is not working, but with content definition = api.selfasserted works. The missing links also are a problem, because they are missing from the page now.
I don't understand the following:
-"To add the "Forgot password" link, you had to create it on page load with JavaScript (see similar example with Terms of Use link)" - In this you are stating that you created "<a href="" id="forgotPassword"></a>"(href will have forgotPassurl) and added your logic from above in the script tags and this is the page with the "signin" correct ?
-const queryString = window.location.search; you are using this to get the params from the link, but this is getting information's for the current page. How this "const forgotPassurl = ""+redirect_uri+"#error=access_denied&error_description=AADB2C90118%0d%0aloginhint="+loginhint+"%0d%0a&state="+state+"&lang="+uilocales+""; " can be used to create a Forgot Password link ? In the ForgotPassword link there are a little bit of different params, maybe the redirect_ uri can be used. Please give some more details. Thank you.
Hi @Silviu_Mihai !
In this scenario, we used the legacy forgot password flow which returns an error message to our own UI. So we specified the redirect_uri param in MSAL when we called our custom policy. In the script, it gets this uri (let's say https://ourcompany.com/dispatch-response) and append everything we need after that.
const forgotPassurl = ""+redirect_uri+"#error=access_denied&error_description=AADB2C90118%0d%0alogin_hint="+login_hint+"%0d%0a&state="+state+"&lang="+ui_locales+"";
The most important thing here is the state property and the syntax of the error itself. It's just so MSAL can correctly handle the return. For our needs, we have an error handler on our client side, that calls the "ResetPassword " policy (again with MSAL) when receiving this error (AADB2C90118). The other parametes such as login_hint and ui_locales are also used for this call. We used the legacy because we have some ID validations we wanted to perform on our side before calling the ResetPassword custom policy with loginRedirect. But this part is more related to MSAL library, not the B2C custom policies.
Thank you for the explanation. That's a way to handle things, but seems a little bit overworked and I think the Microsoft should add an alternative way, but if you need to do some validations ok. Those validations are safety ones (which in general should be taken in consideration) ? Do you think by adding directly the URL (which will call the ResetPassword policy) in the html page, would be unsafe ? Also noticed for the signup page I need to create a separate policy, a split one for signup and one for sign in, so that I can add a direct URL to the html page(signup page). I tried to use this sample: https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-up-deep-link but it will not work. Do you think it is safe enough to be used like this ?