Authsignal provides a simple integration with Azure AD B2C via our Open ID Connect (OIDC) endpoints.
With this integration you can use Azure AD B2C’s custom policies and technical profiles to orchestrate passwordless login and adaptive MFA through Authsignal’s pre-built UI.
The guide below outlines how to use Authsignal as a MFA provider for Azure AD B2C. Authsignal provides other utility endpoints to achieve further customization, see Authsignal’s custom policy code snippets.
Authsignal integrates with Azure AD B2C by acting as an OIDC
provider - this integration model can also be used for
other platforms which support OIDC.
Prerequisites
This guide assumes that you have already set up an Azure AD B2C tenant and are using custom policies. If not, it is recommended to familiarize yourself with Azure AD B2C custom policies and to follow Microsoft’s official getting started with custom policies guide before continuing.
We also assume that you have an Authsignal tenant created, with at least one Authenticator enabled. Enable an Authenticator on your Authsignal tenant here.
Sequence
The following sequence diagram demonstrates the necessary orchestration steps and corresponding requests when using Authsignal as an MFA provider for Azure AD B2C.
Code example
You can find a full code example referenced in this guide on Github.
This guide builds on top of the SignUpOrSignIn policy from the Azure AD B2C starter pack.
Step by step guide
Step 1: Add orchestration steps to your user journey
Add the following five steps to your user journey. These steps should be performed after the user has been identified, but before the user is authenticated and issued a token.
<!-- A. Call Authsignal's OIDC init auth endpoint to generate a short lived token representing the user.
This is a prerequisite before federating the flows to Authsignal via the OIDC Authorize technical profile in the step below -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AuthsignalOidcInitAuth" TechnicalProfileReferenceId="authsignalOidcInitAuth"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AuthsignalOidcAuth" TechnicalProfileReferenceId="authsignalOidcAuth"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isMatchingUserId</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="ShowUserIdMismatchErrorPage" TechnicalProfileReferenceId="SelfAsserted-AuthenticationError" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isChallengeSuccessful</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="ShowChallengeFailedPage" TechnicalProfileReferenceId="SelfAsserted-AuthenticationError" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authsignalEnrolled</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="ShowUserAuthenticatorNotEnrolledErrorPage" TechnicalProfileReferenceId="SelfAsserted-NotEnrolledError" />
</ClaimsExchanges>
</OrchestrationStep>
See the orchestration steps in context of a custom policy file
Step 2: Add the base technical profile
This is the base technical profile that is used to connect to the Authsignal Connect (OIDC) API by setting the authorization header, request body and other necessary configuration for the Azure AD B2C’s RestfulProvider. It is referenced by other technical profiles.
<TechnicalProfile Id="AuthsignalConnectApiBase">
<DisplayName>Authsignal Connect API Base</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
<Item Key="ResolveJsonPathsInJsonTokens">true</Item>
<Item Key="ClaimUsedForRequestPayload">requestBody</Item>
<Item Key="DefaultUserMessageIfRequestFailed">Cannot process your request right now, please try again later.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_AuthsignalSecret" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_AuthsignalSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="requestBody" />
</InputClaims>
</TechnicalProfile>
See the base technical profile in context of a custom policy file
Replace the AUTHSIGNAL_CONNECT_HOSTNAME
and INSERT_AUTHSIGNAL_ACTION
placeholders with the appropriate values.
A list of available hostnames can be found in the OIDC documentation. You should use the hostname where your Authsignal tenant is located.
Technical profile:
<TechnicalProfile Id="AuthsignalOidcInitAuth">
<DisplayName>Authsignal OIDC Init Auth API</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://AUTHSIGNAL_CONNECT_HOSTNAME/init-auth</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="AuthsignalCreateInitAuthRequestBody"/>
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="requestBody"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="token"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AuthsignalConnectApiBase"/>
</TechnicalProfile>
See the init auth technical profile in context of a custom policy file
Input Claims Transformation:
Replace the INSERT_AUTHSIGNAL_ACTION
placeholder with the desired Authsignal action.
<ClaimsTransformation Id="AuthsignalCreateInitAuthRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="userId" />
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="email" />
</InputClaims>
<InputParameters>
<InputParameter Id="action" DataType="string" Value="INSERT_AUTHSIGNAL_ACTION" />
<InputParameter Id="redirectToSettings" DataType="boolean" Value="false" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="requestBody" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
See the init auth input claims transformation in context of a custom policy file
Replace the AUTHSIGNAL_CONNECT_HOSTNAME
and INSERT_AUTHSIGNAL_TENANT_ID
placeholders with the appropriate values.
Technical profile:
<TechnicalProfile Id="AuthsignalOidcAuth">
<DisplayName>Authsignal OIDC Authorize API</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="METADATA">https://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/.well-known/openid-configuration</Item>
<Item Key="client_id">INSERT_AUTHSIGNAL_TENANT_ID</Item>
<Item Key="authorization_endpoint">https://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/auth</Item>
<Item Key="AccessTokenEndpoint">https://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/token</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="token_endpoint_auth_method">client_secret_post</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<Item Key="ResolveJsonPathsInJsonTokens">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AuthsignalSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="token" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="actionCode" PartnerClaimType="action_code" />
<OutputClaim ClaimTypeReferenceId="actionState" PartnerClaimType="action_state" />
<OutputClaim ClaimTypeReferenceId="authsignalUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="authsignalEnrolled" PartnerClaimType="is_enrolled" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AuthsignalCheckIsChallengeSuccessful" />
<OutputClaimsTransformation ReferenceId="AuthsignalCheckMatchingUserId" />
</OutputClaimsTransformations>
</TechnicalProfile>
See the OIDC authorize technical profile in context of a custom policy file
Output Claims Transformations:
<ClaimsTransformation Id="AuthsignalCheckMatchingUserId" TransformationMethod="CompareClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="authsignalUserId" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="operator" DataType="string" Value="EQUAL" />
<InputParameter Id="ignoreCase" DataType="string" Value="true" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isMatchingUserId" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="AuthsignalCheckIsChallengeSuccessful" TransformationMethod="LookupValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="actionState" TransformationClaimType="inputParameterId" />
</InputClaims>
<InputParameters>
<InputParameter Id="ALLOW" DataType="string" Value="true" />
<InputParameter Id="CHALLENGE_SUCCEEDED" DataType="string" Value="true" />
<InputParameter Id="CHALLENGE_FAILED" DataType="string" Value="false" />
<InputParameter Id="BLOCK" DataType="string" Value="false" />
<InputParameter Id="errorOnFailedLookup" DataType="boolean" Value="false" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isChallengeSuccessful" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
See the OIDC authorize output claims transformation in context of a custom policy file
Step 5: Add technical profiles for the error pages
These are basic error pages shown when the authentication challenge is failed. It is using the default api.selfasserted
content definition. You may want to customize this page to suit your branding.
<TechnicalProfile Id="SelfAsserted-NotEnrolledError">
<DisplayName>Not enrolled error</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage" DefaultValue="You must register an authentication method to continue."/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" Required="true"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-AuthenticationError">
<DisplayName>Authentication failed</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage" DefaultValue="Authentication challenge failed. Please try again."/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" Required="true"/>
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
See the error technical profile in context of a custom policy file
Step 6: Declare the claims used by the technical profiles
<ClaimType Id="requestBody">
<DisplayName>requestBody</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="bearerToken">
<DisplayName>bearerToken</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="actionCode">
<DisplayName>actionCode</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="actionState">
<DisplayName>actionState</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="isMatchingUserId">
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="isChallengeSuccessful">
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="authsignalUserId">
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="authsignalEnrolled">
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="errorMessage">
<DisplayName></DisplayName>
<DataType>string</DataType>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
See the claims in context of a custom policy file
Step 7: Store your Authsignal Tenant Secret on Azure AD B2C
Your Authsignal tenant secret is stored as a policy key on Azure AD B2C’s Identity Experience Framework and referenced by our technical profile with the Id B2C_1A_AuthsignalSecret
.
You can find the secret key for your tenant in the Authsignal Admin Portal and add it to your Azure AD B2C tenant as a policy key via the Azure Portal.
Step 8: Upload your custom policy and test your integration
You can now test your integration by navigating to your custom policy in the Azure AD B2C portal and clicking “Run now”. You should see the Authsignal UI for MFA after enrolling your user and for subsequent login attempts.
Congratulations!
You have successfully integrated Authsignal with Azure AD B2C for MFA.
Next steps