Table of Contents

    Book an Appointment

    INTRODUCTION

    During a recent project for a high-growth FinTech SaaS platform, we were tasked with modernizing their Customer Identity and Access Management (CIAM) architecture. The platform required a highly secure, frictionless authentication layer capable of handling diverse user flows. We chose Azure AD B2C custom policies for their deep extensibility.

    While working on the unified user portal, a specific business requirement surfaced. We needed to maintain a single Relying Party (RP) policy using a combined sign-in and sign-up user interface to minimize configuration overhead. However, the marketing team was launching targeted campaigns and required a direct, standalone “Sign-Up” link that bypassed the standard login screen. We encountered a situation where standard out-of-the-box configurations forced users to navigate through the combined page to reach the registration form, causing friction in conversion funnels.

    Finding a way to route users dynamically without duplicating RP files became a critical architectural challenge. This experience inspired this article, aiming to help engineering teams streamline their B2C policies and avoid maintaining duplicate XML files.

    PROBLEM CONTEXT

    In standard Azure AD B2C custom policy architecture, handling authentication efficiently often means utilizing a unified approach. For this FinTech application, we implemented a single Relying Party (RP) policy that triggered a CombinedSignInAndSignUp technical profile. This single-page approach is excellent for general portal access, allowing users to log in or click a “Sign up now” link if they don’t have an account.

    The architecture also heavily relied on complex sub-journeys for password resets and multi-factor authentication (MFA). Maintaining a single RP file was non-negotiable from an engineering standpoint. If we split the logic into two separate RP files—one for sign-in and one for sign-up—we would double our maintenance effort. Every future change to token issuance, MFA routing, or custom claims logic would need to be replicated across multiple entry points, increasing the risk of configuration drift.

    WHAT WENT WRONG

    When the client application attempted to send users directly to the sign-up flow, the limitations of a strict CombinedSignInAndSignUp orchestration step became apparent. By default, Azure AD B2C honors the orchestration sequence exactly as defined in the User Journey.

    Symptoms of this architectural oversight included:

    • Marketing campaign links were routing prospective users to the standard login screen.
    • Conversion rates dropped because users had to hunt for the “Sign up now” link.
    • Initial attempts to pass the standard OIDC prompt=signup parameter were ignored by the combined technical profile, as it inherently defaults to the sign-in view.

    We realized that without a mechanism to intercept the application’s intent and conditionally alter the User Journey at runtime, we would be forced into the technical debt of multi-RP management.

    HOW WE APPROACHED THE SOLUTION

    To solve this, we needed to pass a signal from the client application to the Azure AD B2C policy and act upon it before the UI rendered. We decided to leverage Azure AD B2C Claims Resolvers alongside Orchestration Step Preconditions.

    The diagnostic process involved:

    • Identifying the Intent: We established a custom query string parameter, &intent=signup, which the web application would append to the authorization request when a direct sign-up was required.
    • Capturing the Parameter: We utilized the {OAUTH-KV:intent} claims resolver. This feature allows B2C to read custom key-value pairs from the initial OAuth/OIDC request and map them into a user claim.
    • Conditional Routing: We restructured the initial Orchestration Steps. Instead of immediately presenting the combined UI, we added a hidden claims exchange step to read the intent. Subsequent steps used preconditions to evaluate this claim. If the intent was “signup”, the journey skipped the combined UI and jumped directly to the local account registration profile.

    This approach perfectly balanced business needs with architectural elegance. It is the type of strategic problem-solving you can expect when you hire software developer teams with deep cloud identity expertise.

    FINAL IMPLEMENTATION

    Here is the sanitized technical implementation used to achieve the standalone sign-up link within a single RP policy.

    Step 1: Define the Claim Type

    First, we define a string claim to hold the incoming parameter value.

    <ClaimType Id="clientIntent">
      <DisplayName>Client Intent</DisplayName>
      <DataType>string</DataType>
    </ClaimType>
    

    Step 2: Create a Technical Profile to Extract Intent

    We added a Technical Profile that uses the claims resolver to read the query string.

    <TechnicalProfile Id="Extract-ClientIntent">
      <DisplayName>Extract Intent from Query String</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="clientIntent" DefaultValue="{OAUTH-KV:intent}" AlwaysUseDefaultValue="true" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="clientIntent" />
      </OutputClaims>
    </TechnicalProfile>
    

    Step 3: Update the User Journey with Preconditions

    We modified the User Journey to evaluate the extracted intent. Step 1 extracts the claim. Step 2 handles standard Sign-In (skipping if intent is signup). Step 3 handles standalone Sign-Up (skipping if intent is not signup).

    <UserJourney Id="UnifiedSignInAndSignUp">
      <OrchestrationSteps>
        
        <!-- Step 1: Read Query Parameter -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="ReadIntent" TechnicalProfileReferenceId="Extract-ClientIntent" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Step 2: Combined Sign-In (Skipped if intent == signup) -->
        <OrchestrationStep Order="2" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>clientIntent</Value>
              <Value>signup</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsProviderSelections>
            <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
          </ClaimsProviderSelections>
        </OrchestrationStep>
        <!-- Step 3: Standalone Sign-Up (Skipped if intent != signup) -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
              <Value>clientIntent</Value>
              <Value>signup</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSignUpExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Subsequent steps for token issuance -->
      </OrchestrationSteps>
    </UserJourney>
    

    By simply appending ?intent=signup to the application’s authentication request, the platform routes users effortlessly to the desired screen.

    LESSONS FOR ENGINEERING TEAMS

    Implementing conditional flows in Azure AD B2C yields several valuable takeaways for software architecture:

    • Maintain a Single Source of Truth: Whenever possible, consolidate user journeys. Maintaining a single Relying Party file minimizes the risk of fragmented security policies.
    • Leverage Claims Resolvers: The {OAUTH-KV:...} resolver is a powerful tool for bridging the gap between client application context and identity provider logic.
    • Utilize Preconditions Strategically: Preconditions transform static user journeys into dynamic state machines. Use them to route users based on context, device, or intent.
    • Prioritize UX in Security: Security should not dictate a poor user experience. Flexibility at the identity layer ensures marketing and product goals are met without compromising safety.
    • Plan for Extensibility: This pattern can be expanded to support targeted password reset links or specific MFA triggers. When you hire azure developers for enterprise identity, ensure they design policies with this level of foresight.
    • Design for Seamless Backend Connectivity: Identity flows often trigger downstream events. When you hire dotnet developers for seamless backend integration, pass custom identity claims directly into your application tokens to drive backend business logic efficiently.

    WRAP UP

    By extracting a simple query string parameter and injecting it into the orchestration logic via claims resolvers, we successfully satisfied the business requirement for a standalone sign-up link without sacrificing the maintainability of a unified Relying Party policy. This approach keeps the XML configuration DRY (Don’t Repeat Yourself) while offering maximum flexibility to the client application.

    If your organization is navigating complex identity architectures, cloud modernizations, or custom integrations, contact us to explore how our dedicated remote engineering teams can drive your technical vision forward.

    Social Hashtags

    #AzureADB2C #MicrosoftEntra #CIAM #IdentityManagement #Authentication #CloudSecurity #AzureDevelopers #CustomPolicies #OAuth2 #CyberSecurity #MicrosoftAzure #SoftwareArchitecture #DotNet #DevOps #EnterpriseSecurity #CustomerIdentity #CloudComputing #OIDC #DeveloperCommunity #TechBlog

     

    Frequently Asked Questions