AAD B2C - Add reCaptcha to SignIn page with custom policies

Patrice Côté 186 Reputation points
2023-01-31T19:49:08.12+00:00

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".

Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
19,458 questions
{count} votes

Accepted answer
  1. Marilee Turscak-MSFT 33,801 Reputation points Microsoft Employee
    2023-02-03T22:29:39.5266667+00:00

    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:

    1. When adding the custom field "g-recaptcha-response-toms", it would not render.
    2. After you were able to resolve the first issue and get the information in the form, the "Forgot password" link would not display.

    Solution:

    1. 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.
    2. 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.

    2 people found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Patrice Côté 186 Reputation points
    2023-02-03T14:11:27.26+00:00

    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;