This guide extends our previous guides where we showed you how to integrate Authsignal into Microsoft Azure AD B2C for multi-factor authentication (MFA) and passwordless login
In this guide, we will show you how to add passkey autofill into your Microsoft Azure AD B2C login page.
You can use this guide to add passkey autofill if you are using Authsignal for
MFA or passwordless login.
Prerequisites
Before continuing with this guide, you should first complete the following:
- Replace your login screen with custom html templates. In our example, we have made a copy of the sample templates provided by Microsoft. This is required as we will need to add custom javascript to the login page.
- Set
up custom domains on your Azure AD B2C tenant. You can follow the official documentation
to set up custom domains. This is required as we will need to replace the default
b2clogin.com
domain with the domain where we will register our passkeys, so that
the browser is able to detect available passkeys.
We recommend setting your b2c domain to identity.yourdomain.com
, and
Authsignal custom domain to auth.yourdomain.com
Code example
A full code example referenced in this guide can be found on Github.
You can also refer to the code diff between the previous guide if you only want to see the changes.
Step by step guide
Step 1: Configure the custom domain of your Authsignal tenant
Navigate to Authsignal portal > Settings > Custom domains to configure the custom domain of your Authsignal tenant. We recommend this be a distinct subdomain from your Azure AD B2C tenant domain.
Navigate to Authsignal portal > Authenticators > Passkey to enable the passkey authenticator.
Set the relying party ID to your root domain (e.g. yourdomain.com
).
Set the expected origins for both your Azure AD B2C domain and your Authsignal custom domain. E.g., https://identity.yourdomain.com
and https://auth.yourdomain.com
.
Step 3: Add javascript to the login page html template
In order to enable passkey autofill in our B2C login page, we need add custom javascript into our html template.
In the <head>
tag, add the following script which will:
- Wait for Azure AD B2C to dynamically populate the
api
element with the login from
- Load the Authsignal browser SDK
- Set the
autocomplete
attribute of the login input to username webauthn
<script>
function setWebauthnAttribute() {
var signInInput = document.getElementById("signInName");
var passwordInput = document.getElementById("password");
const nextButton = document.getElementById("next");
if (signInInput) {
signInInput.setAttribute("autocomplete", "username webauthn");
// NOTE: Replace the following values with your Authsignal tenant ID and server URL
var client = new window.authsignal.Authsignal({
tenantId: "AUTHSIGNAL_TENANT_ID",
baseUrl: "AUTHSIGNAL_SERVER_URL",
});
passwordInput.value = "placeholder";
client.passkey
.signIn({ autofill: true })
.then((response) => {
if (response) {
signInInput.value = response.userName;
passwordInput.value = "token-" + response.token;
nextButton.click();
}
})
.catch((error) => {
console.log("error", error);
});
}
}
function loadAuthsignalSdk() {
var script = document.createElement("script");
script.onload = setWebauthnAttribute;
script.src =
"https://unpkg.com/@authsignal/browser@0.5.2/dist/index.min.js";
document.head.appendChild(script);
}
var observer = new MutationObserver(() => {
var apiElement = document.getElementById("api");
if (apiElement) {
loadAuthsignalSdk();
observer.disconnect();
}
});
observer.observe(document, {
attributes: false,
childList: true,
characterData: false,
subtree: true,
});
</script>
This step is optional, and only required if you have a passwordless login flow. If you still allow users to login with their password, skip this step.
Add the following <style>
tag to your html template to hide the password input:
<style>
div:has(> #password) {
position: absolute !important;
visibility: hidden;
}
</style>
Step 6: Enable javascript in your html templates
Add the following <ScriptExecution>
element to our Relying Party policy under the <UserJourneyBehaviors>
element
<ScriptExecution>Allow</ScriptExecution>
See the official Microsoft guide
Step 7: Add the required claim types
<ClaimType Id="isPasskeyChallengeSuccessful">
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="isPasskeyAutofill">
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="passkeyToken">
<DataType>string</DataType>
</ClaimType>
<ClaimsTransformation Id="AuthsignalCreateValidateChallengeRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="passkeyToken" TransformationClaimType="token" />
</InputClaims>
<InputParameters>
<InputParameter Id="action" DataType="string" Value="passkey-verify" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="requestBody" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CheckIsPasskeyAutofillToken" TransformationMethod="StringContains">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="contains" DataType="string" Value="token-" />
<InputParameter Id="ignoreCase" DataType="string" Value="true" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isPasskeyAutofill" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="ExtractPasskeyToken" TransformationMethod="StringReplace">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="oldValue" DataType="string" Value="token-" />
<InputParameter Id="newValue" DataType="string" Value="" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="passkeyToken" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
Step 9: Add new technical profiles to check and validate passkey autofill challenges
<TechnicalProfile Id="CheckIsPasskeyAutofill">
<DisplayName>Check is passkey autofill</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CheckIsPasskeyAutofillToken" />
<InputClaimsTransformation ReferenceId="ExtractPasskeyToken" />
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isPasskeyAutofill" />
<OutputClaim ClaimTypeReferenceId="passkeyToken" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="ValidatePasskeyChallenge">
<DisplayName>Validate passkey challenge</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ServiceUrl">{Settings:AuthsignalServerUrl}/validate</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="AuthsignalCreateValidateChallengeRequestBody"/>
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="requestBody"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isPasskeyChallengeSuccessful" PartnerClaimType="isValid"/>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="userId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AuthsignalConnectApiBase"/>
</TechnicalProfile>
Step 10: Adjust the technical profile used in our CombinedSignInAndSignUp orchestration step
Add the following claims to the OutputClaims
element:
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="isPasskeyAutofill" />
<OutputClaim ClaimTypeReferenceId="isPasskeyChallengeSuccessful" />
Add the following validation technical profiles:
<ValidationTechnicalProfile ReferenceId="CheckIsPasskeyAutofill" />
<ValidationTechnicalProfile ReferenceId="ValidatePasskeyChallenge">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isPasskeyAutofill</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>isPasskeyAutofill</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>passkeyToken</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress_Passwordless">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isPasskeyAutofill</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
Step 11: Add a precondition to the orchestration step which shows an error page if the passkey autofill challenge is not successful
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isPasskeyChallengeSuccessful</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
Step 12: Add a precondition to orchestration steps that should be skipped if passkey autofill is successful
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isPasskeyAutofill</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
Step 13: Upload the updated html template and custom policies, and test the changes
You can now test your integration by navigating to your custom policy in the Azure AD B2C portal and clicking “Run now”, ensuring that you change the base url to your custom domain.
Once you sign up a new user and register a passkey, when the user next lands on the login screen they will be prompted to sign in with their passkey.
Congratulations!
You have successfully added passkey autofill to your Microsoft Azure AD B2C login page.