This post continues a series that provides a walkthrough illustrating how to work with Azure Active Directory B2C custom policies by building one from the ground up. As the series progresses, the topics will serve to show how the different parts of the AAD B2C policy syntax and the underlying framework can be brought together to realize desired functionality. At the conclusion of the series, you should be ready to both understand existing AAD B2C policies and to create new ones for yourself.

The topic areas being covered in this series are:

Topic Focus Area
Introduction
Hello World! Returning a simple message with a custom policy
Hello [Your Name Here]! Custom policy elements and syntax, how to collect and work with user input
Hello! (But Only If You Have an Access Code) How to perform input validation
Hello! (NOW WITH REST!) <– You are here How to make HTTP requests to external services for validation or information exchange
Hello! Let’s Add a User How to use the backing Azure Active Directory database to store and retrieve user information
Hello! Please Either Sign In or Sign Up How to either sign in an existing user or allow a new user to sign up
Hello! It’s Time To Clean Up a Bit How to use multiple files and a policy file hierarchy to manage and reuse policy content


NOTE

This walkthrough is intended for developers who want to learn how to leverage AAD B2C Custom Policies. The content assumes you understand basic AAD B2C concepts such as how to make use of the built-in policies to implement basic user management and authentication tasks. Basic functional tutorials for AAD B2C can be found in the online documentation here.

Catching Up

In the previous posts in this series you have seen how you could assemble an Azure AD B2C custom policy that could collect, restrict, and validate user input. The custom policy was also capable of executing different logic depending on the input that had been provided by the user. At the end of the process, the policy would return a JWT token containing a customized “Hello World” message as well as some of the information entered by the user.

Up to this point, the policy execution has been fairly self-contained. The business logic to be followed was completely encoded within the policy “code”. This included both the list of valid “Company Account” domains, as well as the not-so-brilliant hardcoded “12345” valid access code. There are several times when the functionality in your policy will need to extend beyond what’s simply coded within it. Some examples include:

  • Interacting with application logic that is implemented outside of the Azure AD B2C policies
  • Implementing functionality that is not part of the AAD B2C IEF framework
  • Calling an external service to obtain additional information or perform an activity

In this post we will once again extend our “Hello World” policy. We will first look at how we can “bridge” to application logic that lives outside of the policy itself and is implemented behind an HTTP endpoint. Next, we will see how we can use HTTP requests to supplement the functionality provided by the IEF runtime and implement features that are missing or otherwise unavailable from within a policy. Finally, we will see how you error information can be returned to a policy from an HTTP endpoint in order to use an HTTP request to help with data validation.

NOTE:
Despite some naming that is used by the IEF framework, as we go forward I am going to avoid the word REST as much as I can when describing what we are doing here, and am instead emphasizing that we are making HTTP requests. The label REST has some implications about specific verbs, actions, and URL syntax, and the degree to which these need to be adhered to is the subject of much debate. The IEF implementation is more of an HTTP client and there are some limitations of the implementation which may prevent you from accessing “truly” RESTful services – for example you can currently only use GET and POST verbs. Don’t panic if you have an API already developed that does not align to these limitations! One common solution is to provide a “shim” HTTP request implementation between your policy and your API to handle any mismatches.

We will continue working with the example policy that we have developed throughout the previous posts in this series. You can download a copy of that file here, being sure to update the TenantId, PolicyId, and PublicPolicyUri attributes to the correct values from your AAD B2C tenant, and (if you used different names) replacing your own cryptographic key names in the JWTIssuer TechnicalProfile element.

If you find you are struggling to follow along or run into a problem that you cannot quite solve, a completed version of the policy that is being developed in this post is also available for download. You can download a copy of that file here, being sure to update the TenantId, PolicyId, and PublicPolicyUri attributes to the correct values from your AAD B2C tenant, and (if you used different names) replacing your own cryptographic key names in the JWTIssuer TechnicalProfile element. Also be sure to replace the URL to your HTTP API location (indicated with YOUR_API_URL in the downloadable policy file).

In addition to the policy file, the Visual Studio ASP.NET Core Web solution that will be used to create and deploy the Web API used in this post is also available for download. You can download a copy of that project here. Notice that the project includes Swagger (OpenAPI) metadata and UI (via the Swashbuckle library), as well as Application Insights integration.

A Note About HTTP Endpoint Hosting

When you are developing or demonstrating Azure AD B2C custom policies that make HTTP requests, you will normally not be able to point your policy directly to an HTTP endpoint running on your machine at a localhost URL. Azure AD B2C custom policies currently execute in Azure, and cannot directly access localhost or otherwise locally scoped addresses (there are some workarounds for this that will be discussed shortly.)

As a result, many of the development examples, demos, tutorials, etc (including the work I did myself for this post) will seek an inexpensive solution for deploying HTTP endpoints and will use something like an Azure Function running in a Consumption Plan or an Azure App Service running in a Free Tier. And for limited dev/demo/test scenarios, those choices fall somewhere in the spectrum between “awesome” and “great”.

However, these solutions are likely not going to be adequate for production scenarios. Many of these low-cost options include an idea of allowing the service to become “dormant” after a period of inactivity, and can incur a substantial (30 seconds+) “cold start” delay when activity resumes. Depending on how this is used in your policy, this can result in a degraded user experience.

Therefore, it is important to consider things like cold-start responsiveness when planning the hosting strategy for the HTTP endpoints that your service will send requests to. If you’re using App Service you may need to consider at least a Basic or higher tier so that you can enable “Always On”. If you are using Azure Functions, you should consider either hosting your Functions in a Dedicated App Service Plan using a Basic or higher tier with Always On enabled or using a Functions Premium Plan for hosting.

NOTE:
I mentioned above that code running on localhost or other internal addresses cannot be accessed directly by your policies. There are options that allow you to “tunnel” publicly accessible addresses to code running in localhost. One example would be ngrok, which lets you run a small agent on your local computer that you can configure to connect your on-machine HTTP endpoint to a public address that you can then have your policies call. This approach can be particularly helpful for debugging interactions between your policy and your service implementation.

Bridging to Application Logic

The first HTTP request that we will make will be to leverage some of our own application logic, but implemented outside of our policy. Recall that our policy up to this point includes business logic that checks if the email address provided by the user for a “Corporate Account” belongs to one of a few well-known domains that have been hard-coded into the policy itself. What if instead of a hardcoded list, we wanted to obtain this list instead from a location where the list could be dynamic or otherwise could be changed without requiring a redeployment of our policy?

The HTTP API

The examples in this post will be implemented using an HTTP API written in C# with the ASP.NET Core 3.1 framework. Feel free to implement the APIs used by your custom policies in your preferred language and on your preferred platform, since the HTTP protocol will generally abstract away the specific implementation.

The following API method has been created to provide a list of valid Company Domains:

[HttpGet("CompanyDomains")]
public IActionResult GetCompanyDomains()
{
    var domainsResponse = new ValidDomainsResponse
    {
        // The domain provider obtains a list of domains, perhaps from a database call, etc.
        Domains = _validValuesProvider.GetValidDomains()
    };
    var result = Ok(domainsResponse);
    return result;
}

public class ValidDomainsResponse
{
    public IEnumerable<String> Domains { get; set; }
}

The exact source of the list of valid domains is not important – the API could check a database, read a configuration file, or could call another downstream service to obtain the values. The important details are:

  • The GetCompanyDomains method is accessed via an HTTP GET request at the path defined by the API’s address followed by CompanyDomains.
  • The method will return an HTTP 200 (OK) result with a JSON payload that includes an element named Domains.
  • That Domains node will contain an array of valid domain names.

The Restful Provider Technical Profile

To make HTTP requests from a custom policy you use a RESTful Technical Profile. To configure this kind of Technical Profile, you need to specify:

  • Input Claims that indicate the inputs that will be sent as part of an HTTP request.
  • Output Claims that indicate the results returned in the response to an HTTP request.
  • Metadata that specifies the URL for the HTTP request, how inputs will be sent in the request (this choice also indicates which HTTP verb will be used for the request), what type of authentication will be used for the request, and other behaviors.
  • The specific authentication information to be used for the request, depending on the selected authentication approach.

NOTE:
As discussed in the introduction, even though the name of the Technical Provider is RESTful, it perhaps would be more accurate to call it an HTTP Client Technical Profile. At present, your parameter specifications will either result in GET or POST requests. Furthermore, it is not limited strictly to making calls with REST semantics. For example, making RPC calls via HTTP POST requests is quite normal (if you need to make a call that would normally require an HTTP verb that is not available to you, for example.)

Let’s update our policy to retrieve a list of allowable domains from the endpoint we defined above. First, let’s add a new claim declaration within the Claims Schema section of your policy:

<!-- HTTP Request Technical Profiles -->
<ClaimType Id="validDomains">
  <DataType>stringCollection</DataType>
</ClaimType>

Now add a new ClaimsProvider block to the Claims Providers section in your profile. This way we can group all of our HTTP Profiles together:

<!-- HTTP Request Technical Profiles -->
<ClaimsProvider>
  <!--
  The technical profiles defined in this section perform HTTP requests.
  -->
  <DisplayName>Sample HTTP Request Technical Profiles</DisplayName>

  <!-- HTTP Request Technical Profiles -->
  <TechnicalProfiles>

  </TechnicalProfiles>
</ClaimsProvider>

Inside of the TechnicalProfiles element in that block, add the following initial Technical Profile, being sure to replace the https://b2ccustompolicies.azurewebsites.net part of the ServiceUrl entry below with the corresponding address which you have used to deploy your API implementation:

<TechnicalProfile Id="HttpCompanyDomainsCollector">
  <DisplayName>Collect Sample Company Domains Technical Profile</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="ServiceUrl">https://b2ccustompolicies.azurewebsites.net/Api/CompanyDomains</Item>
    <Item Key="SendClaimsIn">QueryString</Item>
    <Item Key="AuthenticationType">None</Item>
    <Item Key="AllowInsecureAuthInProduction">true</Item>
  </Metadata>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="validDomains" PartnerClaimType="domains" Required="true"/>
  </OutputClaims>
</TechnicalProfile>

This Technical Profile is configured to use the RestfulProvider. It then specifies a Metadata block which defines how the HTTP endpoint will be called. The URL for the endpoint is https://b2ccustompolicies.azurewebsites.net/Api/CompanyDomains (remember – you should change this for your own endpoint).

The input parameters for the request (if there were any – there are none for this endpoint) will be included in the request’s QueryString, which indicates that IEF runtime will issue this call as a GET request. The following table shows the (current) available options for the SendClaimsIn entry and what kind of request they will result in:

SendClaimsIn Value Request Type Request Verb
Body Input claims are sent as JSON in the HTTP request body. The property name to be used for each element in the JSON payload is defined by the PartnerClaimType value (or ClaimType if a PartnerClaimType is not specified). POST
Form Input claims are sent in the request body as key1=value&key2=value2, where the PartnerClaimType or ClaimType names are used as keys. POST
Header Input claims are sent as request header values. GET
QueryString Input claims are sent in the request query string. GET

NOTE:
For the purposes of demonstration only, there is no authentication support configured on the API endpoint, which requires us to either specify the AllowInsecureAuthInProduction value in the Technical Profile Metadata or to configure the policy’s Deployment Mode to Development. For production deployments, it is strongly recommended that you configure some form of authentication for the API endpoints that you define, with client certificate-based authentication being the recommended approach. You can read more about configuring client-certificate authentication in the Azure AD B2C documentation.

The last element that is specified in this Technical Profile is the Output Claims collection. For this profile, it consists of a single Output Claim specification which references the validDomains String Collection claim that we defined previously. Notice that a PartnerClaimType attribute is configured, mapping the name of the domains element that will be included in the response from the HTTP endpoint to the validDomains claim name being used in the policy.

Now let’s use this Technical Profile in our policy. Locate the ValidationTechnicalProfiles block in the UserInformationCollector Technical Profile. Insert the following new Validation Technical Profile below BEFORE the existing Validation Technical Profile that references the CheckCompanyDomain Technical Profile:

<ValidationTechnicalProfile ReferenceId="HttpCompanyDomainsCollector">
  <Preconditions>
    <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
      <Value>accountType</Value>
      <Value>company</Value>
      <Action>SkipThisValidationTechnicalProfile</Action>
    </Precondition>
  </Preconditions>
</ValidationTechnicalProfile>

In that same UserInformationCollector Technical Profile, add the following Output Claim entry:

<OutputClaim ClaimTypeReferenceId="validDomains"/>

Add the same Output Claim declaration to the Output Claims in the Relying Party section of the policy. You probably would not do this for a Production deployment, but for now we are doing it here to help us troubleshoot and see if we are in fact fetching the list from our HTTP endpoint.

Now upload the updated policy and run it. Enter user information, being sure to select Company Account as the account type (recall that the account types are still validating against the original list at this point, so be sure that the email address you indicate is for one of the domains indicated in the policy file itself.) The claim that is returned to you should contain the domains defined in your API implementation.

...
{
  "accountType": "company",
  "email": "[email protected]",
  "validDomains": [
    "yourdomain.com",
    "someotherdomain.com",
    "wintellect.com",
    "test.com"
  ],
}
//(This content has been trimmed for brevity)

Supplementing Functionality

The next thing we will want to do is to validate the domain in the user’s email address against the list of valid domains we just received from our API call. Your first instinct might be to check for a Claims Transformation that can be used to see if a StringCollection contains the value in a given claim. Sure enough, there is a StringCollectionContains Transformation that looks promising.

Except that the StringCollectionContains Transformation only supports locating a static value within a String Collection. As of this writing, there is no Claims Transformation that can be used to check if a String Collection contains a value from a given claim. We’re going to have to implement this ourselves in an HTTP API and have our policy call into that instead.

NOTE:
It turns out that there is a bit of a convoluted way to do this without using an external HTTP call (AKA a HACK). You can find the specific details in this GitHub issue where I previously documented this “solution.”

The HTTP API

The following API method should provide an analog to the StringCollectionContains implementation where we can pass in a String Collection, a String Claim, and a preference as to whether or not we should ignore the case when doing the comparison:

[HttpPost("StringCollectionContainsClaim")]
public IActionResult CheckStringCollectionContainsClaim([FromBody] ContainsClaimRequest request)
{
    var comparisonType = request.ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
    var found = request.inputClaim.Any(x => x.Equals(request.item, comparisonType));
    var result = new ContainsClaimResponse { outputClaim = found };
    return Ok(result);
}

public class ContainsClaimRequest
{
    public String[] inputClaim { get; set; } 
    public String item { get; set; }
    public Boolean ignoreCase { get; set; }
}

public class ContainsClaimResponse
{
    public Boolean outputClaim { get; set; }
}

Notice that this API method is implemented as an HTTP POST method and expects its parameters to be provided in the request body. Also note that the input and output names are configured to match the names used in the StringCollectionContains Claims Transformation. At the conclusion of the request, an HTTP 200 (OK) response is returned with a payload that includes a value called outputClaim set to either true or false to indicate if the value was found or not.

Calling the API

First, we will need to add a new claim declaration within the Claims Schema section of the policy to receive the result of the HTTP request:

<ClaimType Id="isValidDomain">
  <DataType>boolean</DataType>
</ClaimType>

Locate the Claims Provider section you added to contain the HTTP request Technical Profiles and add the new Technical Profile below, being sure to replace the https://b2ccustompolicies.azurewebsites.net part of the ServiceUrl entry with the corresponding address which you have used to deploy your API implementation:

<TechnicalProfile Id="HttpStringCollectionContainsClaim">
  <DisplayName>Use an HTTP call to determine if a String Collection contains a Claim value</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="ServiceUrl">https://b2ccustompolicies.azurewebsites.net/Api/StringCollectionContainsClaim</Item>
    <Item Key="SendClaimsIn">Body</Item>
    <Item Key="AuthenticationType">None</Item>
    <Item Key="AllowInsecureAuthInProduction">true</Item>
  </Metadata>
  <InputClaimsTransformations>
    <InputClaimsTransformation ReferenceId="GetDomainFromEmail"/>
  </InputClaimsTransformations>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="validDomains" PartnerClaimType="inputClaim"/>
    <InputClaim ClaimTypeReferenceId="domain" PartnerClaimType="item"/>
  </InputClaims>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="isValidDomain" PartnerClaimType="outputClaim" Required="true"/>
  </OutputClaims>
  <OutputClaimsTransformations>
    <OutputClaimsTransformation ReferenceId="AssertIsValidDomainIsTrue"/>
  </OutputClaimsTransformations>  
</TechnicalProfile>

This Restful Technical Profile indicates that claims will be sent in the request Body, thus resulting in an HTTP POST request. The GetDomainFromEmail Input Claims Transformation is used to populate the domain claim from the email address input by the user. Then the Input Claims section is used to specify the structure of the payload that will be included in the request’s body.

First, the validDomains claim is provided with a name of inputClaim to align to the syntax expected by the HTTP request endpoint. Similarly, the domain claim is provided with a name of item, as expected by the request. The boolean response from the request is returned to the isValidDomain claim by mapping it back from the response payload’s outputClaim element.

Finally, to line up to the same “throw an error if it does not match” behavior that the Claims Transformation lookup implementation had, we need to include an Output Claims Transformation that throws an error if the isValidDomain claim result is not set to true.

NOTE:
Instead of relying on a Claims Transformation to generate an error condition, the HTTP endpoint can itself return an error code, rather than just returning false. For now we are returning true/false to align to the functionality of the existing StringCollectionContains Claims Transformation. Returning an error code and relevant error information from an HTTP request will be explained in the next section.

I mentioned that we need to apply a Claims Transformation to generate an error when the result of the HTTP request is false. This is handled with an AssertBooleanClaimIsEqualToValue Claims Transformation. Go back up to the Building Blocks section of the policy and add the following to the Claims Transformations block:

<ClaimsTransformation Id="AssertIsValidDomainIsTrue" TransformationMethod="AssertBooleanClaimIsEqualToValue">
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="isValidDomain" TransformationClaimType="inputClaim" />
  </InputClaims>
  <InputParameters>
    <InputParameter Id="valueToCompareTo" DataType="boolean" Value="true" />
  </InputParameters>
</ClaimsTransformation>

Now we need to update the Self-Asserted Technical Profile that is responsible fro collecting the input that we are validating. Locate the UserInformationCollector Technical Profile and change the second ValidationTechnicalProfile element to reference the HttpStringCollectionContainsClaim Technical Profile instead of the CheckCompanyDomain Technical Profile:

<ValidationTechnicalProfile ReferenceId="HttpStringCollectionContainsClaim">
<!--<ValidationTechnicalProfile ReferenceId="CheckCompanyDomain">-->

Finally, a different error message will be returned by the AssertBooleanClaimIsEqualToValue Claims Transformation than the error returned by the LookupValue Claims Transformation. Add the following Metadata item to the UserInformationCollector Technical Profile to provide the new error message:

<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">The provided email address was not from a valid company.</Item>

Upload the updated policy and run it. Enter user information, being sure to select Company Account as the account type and enter an email address from a domain that is NOT included in the list specified by your HTTP API.

Checking the Users' Domain with an HTTP Request

Checking the users’ domain with an HTTP request

Try again, but with a valid email domain instead. The User Journey should now complete successfully.

Validating Input

The last sample task that we want to offload to our external HTTP service is to have it help us properly validate the access code that a user will provide if they select Individual Account as their Account Type. While 12345 may have worked for President Skroob’s luggage in Spaceballs, it probably won’t hold up too well in a production application.

Instead of having the HTTP service return a user’s combination back to the policy and then have the policy validate the input, we are instead going to have the verification happen entirely within the HTTP request implementation. Either the request will complete successfully and return a status code of 200 (OK), or it will return an HTTP 409 (Conflict) response code with some additional error information.

NOTE:
The examples used in the previous two sections – obtaining a list of valid domains and then separately ensuring that the provided domain is in the list – could certainly have been accomplished as a single HTTP request with the technique being shown here, instead of the two distinct calls that were used above. There is no technical reason why the approach to keep them as separate calls was selected. Instead, they were presented this way in order to better illustrate the different concepts being discussed in this post.

The HTTP API

The following API method implementation expects a POST request with a payload that specifies the user’s email address and the access code that they entered. It then uses the email address to obtain the actual access code that had been presumably stored somewhere for that user (or in the case of the downloadable demo, yet another hardcoded value). The retrieved code is compared to the code that the user entered, and if they match, the method returns an HTTP 200 (OK) response.

If the codes do not match, an ErrorResponse object is constructed. The attributes of the ErrorResponse type are taken from the Restful Provider documentation and are used by the IEF runtime to provide information back to the end user and/or the developer.

[HttpPost("VerifyAccessCode")]
public IActionResult VerifyAccessCode([FromBody] VerifyAccessCodeRequest request)
{
    // GetValidAccessCodeForUser an acces code given a user email adderss, perhaps from a database call, etc.
    var validAccessCodeForUser = _validValuesProvider.GetValidAccessCodeForUser(request.userEmail);

    if (String.Equals(request.accessCode, validAccessCodeForUser, StringComparison.OrdinalIgnoreCase))
    {
        return Ok();
    }

    // The codes did not match - return an error
    var errorContent = new ErrorResponse
    {
        version = "1.0",
        status = 409,
        code = "errorCode",
        requestId = "requestId",
        userMessage = "The provided code is invalid for the current user.",
        developerMessage = $"The provided code {request.accessCode} does not match the expected {validAccessCodeForUser} code for user {request.userEmail}.",
        moreInfo = "https://someurl.com/moreinformation"
    };
    var errorResult = new ConflictObjectResult(errorContent);
    return errorResult;
}

public class VerifyEmailRequest
{
    public String userEmail { get; set; }
    public String accessCode { get; set; }
}

public class ErrorResponse
{
    public String version { get; set; }
    public Int32 status { get; set; }
    public String code { get; set; }
    public String userMessage { get; set; }
    public String developerMessage { get; set; }
    public String requestId { get; set; }
    public String moreInfo { get; set; }
}

Notice that to report an error, HTTP 409 (Conflict) is the result code that the runtime expects. Within the ErrorResponse that is returned, version, status, and userMessage are the only required values. Normally, the userMessage value will be shown to the end-user in the event of an error, though there is a possible alternate message that can be displayed.

If the policy is configured for it (more on this in a moment), IEF will use the other ErrorResponse members. These include code, requestId, developerMessage, and moreInfo. The code value is used to return a specific error code. Request ID returns an identifier for the specific request that made the call (this can be useful for telemetry correlation). The Developer Message is usually a more complete version of the User Message value in order to provide additional insight into the specific problem or error. Finally, the More Info value is used to return a URI that links to a Web page where additional information can be found.

Notice that in the example above the User Message only specifies generic information about the mismatched access code. However, the Developer Message includes additional details such as the provided incorrect access code, the expected access code, and the user’s email address.

Calling the API

In this case there are no new claims to declare, since all of the logic happens within the HTTP request. If the request is successful, nothing is returned. If it is not, an error gets surfaced to the user interface.

Locate the Claims Provider section you added to contain the HTTP request Technical Profiles and add the new Technical Profile below, being sure to replace the https://b2ccustompolicies.azurewebsites.net part of the ServiceUrl entry with the corresponding address which you have used to deploy your API implementation:

<TechnicalProfile Id="CheckAccessCodeViaHttp">
  <DisplayName>Check that the user has entered a valid access code by using Claims Transformations</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="ServiceUrl">https://b2ccustompolicies.azurewebsites.net/Api/VerifyAccessCode</Item>
    <Item Key="SendClaimsIn">Body</Item>
    <Item Key="AuthenticationType">None</Item>
    <Item Key="AllowInsecureAuthInProduction">true</Item>
  </Metadata>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="userEmail"/>
    <InputClaim ClaimTypeReferenceId="accessCode" PartnerClaimType="accessCode"/>
  </InputClaims>
</TechnicalProfile>

The Technical Profile is configured to send its claims in the request body, so the request will use the HTTP POST verb. The email claim is mapped to the expected userEmail value and the accessCode claim is mapped to an input with the same name.

Now we need to update the Self-Asserted Technical Profile that is responsible fro collecting the input that we are validating. Locate the AccessCodeInputCollector Technical Profile and change the ValidationTechnicalProfile element to reference the CheckAccessCodeViaHttp Technical Profile instead of the CheckAccessCodeViaClaimsTransformationChecker Technical Profile:

<ValidationTechnicalProfiles>
  <ValidationTechnicalProfile ReferenceId="CheckAccessCodeViaHttp"/>
  <!--<ValidationTechnicalProfile ReferenceId="CheckAccessCodeViaClaimsTransformationChecker"/>-->
</ValidationTechnicalProfiles>

Upload the updated policy and run it. Enter user information, being sure to select Individual Account as the account type and enter an invalid access code for the current user. Notice the error. Now try a code that is valid (depending on your API implementation – with the demo API implementation, “67890” should work for all users.)

Checking the Users' Access Code with an HTTP Request - User Message

Checking the users’ access code with an HTTP request – user message

Enabling Debug Mode

In order to see the other ErrorResponse members previously mentioned you need to put the Restful Technical Profile into Debug Mode. Add the following line to the CheckAccessCodeViaHttp Technical Profile’s Metadata section:

<Item Key="DebugMode">true</Item>

Now once again upload the policy and run it, enter the user information, select Individual Account as the account type, and enter an invalid access code for the current user. You should see the full Developer Message displayed, as well as the error Code, Request ID, and More Info values displayed.

Checking the Users' Access Code with an HTTP Request - Developer Message

Checking the users’ access code with an HTTP request – developer message

Working with Complex Request Payloads

If the HTTP endpoint that you need to call has requirements for its data payload that are a more complex than just the field/value types of data that we have shown so far, you do have a couple of options. The first approach you can consider is to develop and use an intermediate HTTP endpoint that can accept simple parameters, construct the more complex payload, and then relay your request with the reformatted inputs.

Sometimes using an intermediate HTTP request is not ideal – it is an additional responsibility, something else that can break, adds additional latency, etc. The second option is that you can actually use a combination of Claims Transformations and REST Claims Provider settings to build a JSON payload form within your policy and send that over as the Body content as part of a POST request. Rather than relying on PartnerClaimType and ClaimType values to basically generate key-value pairs in a JSON object, custom policies can use the GenerateJson Claims Transformation to put either claims or constant values into specific positions within a JSON payload with property names that you specify. Consider for example the following Claims Transformation configuration:

<ClaimsTransformation Id="ExampleGenerateJson" TransformationMethod="GenerateJson">
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="message.recipients.0.emailAddress" />
 </InputClaims>
  <InputParameters>
    <InputParameter Id="message.greeting" DataType="string" Value="Hello World!"/>
  </InputParameters>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="requestBody" TransformationClaimType="outputClaim"/>
  </OutputClaims>
</ClaimsTransformation>

Assuming the email claim has a value of [email protected], this Claims Transformation will generate the following JSON and place it into the requestBody claim:

{
  "message": {
    "recipients": [
      {
        "emailAddress": "[email protected]" 
      }
    ],
    "greeting": "Hello World!"
  }
}

Once you have a claim that contains the JSON payload that you want to send, you can configure the RESTful Technical Profile to send that claim as a JSON payload instead of using the claim to populate the payload that it generates itself by specifying the ClaimUsedForRequestPayload key in the Technical Profile’s Metadata:

<Item Key="ClaimUsedForRequestPayload">requestBody</Item>

This post does not include a detailed example for how to implement this approach to dealing with complex HTTP request inputs. Instead, I’d refer you to the excellent writeup in the Azure AD B2C custom policy documentation that describes how to send an email by making an HTTP request to an instance of the SendGrid service. SendGrid is a 3rd party service that provides an API you can use to programmatically send emails – among several other features. Not only does this example illustrate sending complex payloads as part of an HTTP request, it also illustrates using a 3rd party API rather than one which you have developed yourself.

Recap

In this post you saw how you could use HTTP requests to widen the reach of your policies. You saw how you could make calls to your own API implementations to retrieve values, how you could provide functionality not included in the IEF runtime, and how to return errors to provide validation. We also briefly discussed sending more complex JSON payloads as part of an HTTP request.

If you had any problems following along or if you ran into a problem that you could not quite solve, a completed version of the policy that was developed in this post is available for download. You can download a copy of that file here, being sure to update the TenantId, PolicyId, and PublicPolicyUri attributes to the correct values from your AAD B2C tenant, and (if you used different names) replacing your own cryptographic key names in the JWTIssuer TechnicalProfile element. Also be sure to replace the URL to your HTTP API location (indicated with YOUR_API_URL in the downloadable policy file).

In addition to the policy file, the Visual Studio ASP.NET Core Web solution that was used to create and deploy the Web API used in this post is also available for download. You can download a copy of that project here. Notice that the project includes Swagger (OpenAPI) metadata and UI (via the Swashbuckle library), as well as Application Insights integration.

In the next post in this series we will introduce another new Technical Profile when we see how we can use the Azure Active Directory Technical Profile to use the information entered by our users to create new user entries in our Azure AD Tenant.