Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Applies to:
External tenants (learn more)
In this tutorial, you learn how to add multifactor authentication (MFA) in your Angular single-page application (SPA) by using native authentication's JavaScript SDK.
Just like in strong authentication method registration, MFA flow occurs in three scenarios:
- During sign-in: The user signs in and has a strong authentication method registered.
- After sign-up: After the user completes sign-up, they proceed to sign in. New users need to register a strong authentication method before any MFA challenge. Because the strong authentication method also gets verified during registration, they might not be prompted for an additional MFA challenge.
- After self-service password reset (SSPR): The user successfully resets their password and automatically proceeds to sign in. If the user has a strong authentication method registered, they're prompted to complete MFA challenge.
When MFA is required, the user chooses an MFA challenge method from a list of registered methods. Available options are email one-time passcode, SMS one-time passcode, or both, depending on what the user previously registered.
The flow diagram below illustrates the three scenarios:
Prerequisites
- Complete the steps in sign up, sign in, password reset and register strong authentication method tutorials.
- Visual Studio Code or another code editor.
- Node.js.
- Enable multifactor authentication (MFA) for your app.
Enable app to handle multifactor authentication
To enable MFA in your Angular app, update the app configuration by adding the required capability:
Locate the src/app/config/auth-config.ts file.
In the
customAuthobject, add or updatecapabilitiesproperty to include themfa_requiredvalue in the array as shown in the following code snippet:const customAuthConfig: CustomAuthConfiguration = { customAuth: { ... capabilities: ["mfa_required"], ... }, ... };
The capability value mfa_required informs Microsoft Entra that your app can handle an MFA flow. Learn more about native authentication challenge types and capabilities.
Create UI components
You require form components in your app to support MFA flow, such as to select MFA challenge method and submit MFA challenge.
Create multifactor authentication method selection form
In in your console, navigate to the src/app/components/shared folder, then use Angular CLI to create a component such as mfa-auth-method-selection-form by using the following command:
ng generate component mfa-auth-method-selection-formThis command generates mfa-auth-method-selection-form.component.html and mfa-auth-method-selection-form.component.ts files in the folder src/app/components/shared/mfa-auth-method-selection-form/.
Open the mfa-auth-method-selection-form.component.ts file, then replace its contents with the content in mfa-auth-method-selection-form.component.ts.
Open the mfa-auth-method-selection-form.component.html file, then add the contents in mfa-auth-method-selection-form.component.html.
Create multifactor authentication challenge form
In in your console, navigate to the src/app/components/shared folder, then use Angular CLI to create a component such as mfa-challenge-form by using the following command:
ng generate component mfa-challenge-formThis command generates mfa-challenge-form.component.ts and mfa-challenge-form.component.html files in the folder src/app/components/shared/mfa-challenge-form/.
Open the mfa-challenge-form.component.ts file, then replace its contents with the content in mfa-challenge-form.component.ts.
Open the mfa-challenge-form.component.html file, then add the contents in mfa-challenge-form.component.html.
Handle multifactor authentication during sign-in
Update the src/app/components/sign-in/sign-in.component.ts file to enable your app to handle MFA flow during sign-in. See the complete code in sign-in.component.ts:
Import the required types and components as shown in the following code snippet:
import { AuthenticationMethod, MfaAwaitingState, MfaVerificationRequiredState, } from "@azure/msal-browser/custom-auth"; import { MfaAuthMethodSelectionFormComponent } from "../shared/mfa-auth-method-selection-form/mfa-auth-method-selection-form.component"; import { MfaChallengeFormComponent } from "../shared/mfa-challenge-form/mfa-challenge-form.component"; @Component({ selector: "app-sign-in", templateUrl: "./sign-in.component.html", standalone: true, imports: [ ... MfaAuthMethodSelectionFormComponent, MfaChallengeFormComponent, ... ], })Add new state variables for MFA:
export default function SignIn() { ... // MFA states const [mfaAuthMethods, setMfaAuthMethods] = useState<AuthenticationMethod[]>([]); const [selectedMfaAuthMethod, setSelectedMfaAuthMethod] = useState<AuthenticationMethod | undefined>(undefined); const [mfaChallenge, setMfaChallenge] = useState(""); // ... initialization code }Update the
startSignIn,handlePasswordSubmit, andhandleCodeSubmitfunctions to check if MFA is required:async startSignIn() { const client = await this.auth.getClient(); const result: SignInResult = await client.signIn({ username: this.username }); ... if (result.isMfaRequired()) { this.showMfaAuthMethods = true; this.showPassword = false; this.showCode = false; this.showAuthMethodsForRegistration = false; this.showChallengeForRegistration = false; this.showMfaChallenge = false; this.mfaAuthMethods = result.state.getAuthMethods(); // Set default selection to the first MFA auth method this.selectedMfaAuthMethod = this.mfaAuthMethods.length > 0 ? this.mfaAuthMethods[0] : undefined; this.signInState = result.state; } ... } async submitPassword() { if (this.signInState instanceof SignInPasswordRequiredState) { const result = await this.signInState.submitPassword(this.password); ... if (result.isMfaRequired()) { this.showMfaAuthMethods = true; this.showPassword = false; this.showCode = false; this.showAuthMethodsForRegistration = false; this.showChallengeForRegistration = false; this.showMfaChallenge = false; this.mfaAuthMethods = result.state.getAuthMethods(); // Set default selection to the first MFA auth method this.selectedMfaAuthMethod = this.mfaAuthMethods.length > 0 ? this.mfaAuthMethods[0] : undefined; this.signInState = result.state; } ... } } async submitCode() { if (this.signInState instanceof SignInCodeRequiredState) { const result = await this.signInState.submitCode(this.code); ... if (result.isMfaRequired()) { this.showMfaAuthMethods = true; this.showPassword = false; this.showCode = false; this.showAuthMethodsForRegistration = false; this.showChallengeForRegistration = false; this.showMfaChallenge = false; this.mfaAuthMethods = result.state.getAuthMethods(); // Set default selection to the first MFA auth method this.selectedMfaAuthMethod = this.mfaAuthMethods.length > 0 ? this.mfaAuthMethods[0] : undefined; this.signInState = result.state; } } }In each of the functions, notice that we check if MFA is required by using the following code snippet:
if (result.isMfaRequired()) {...}Add the handler for MFA challenge selection:
async submitMfaAuthMethod() { this.error = ""; this.loading = true; if (!this.selectedMfaAuthMethod) { this.error = "Please select an authentication method."; this.loading = false; return; } if (this.signInState instanceof MfaAwaitingState) { const result = await this.signInState.requestChallenge(this.selectedMfaAuthMethod.id); if (result.isFailed()) { if (result.error?.isInvalidInput()) { this.error = "Incorrect verification contact."; } else { this.error = result.error?.errorData?.errorDescription || "An error occurred while verifying the authentication method."; } } if (result.isVerificationRequired()) { this.showMfaAuthMethods = false; this.showMfaChallenge = true; this.signInState = result.state; } } this.loading = false; }Add the handler for MFA challenge verification:
async submitMfaChallenge() { this.error = ""; this.loading = true; if (!this.mfaChallenge) { this.error = "Please enter a code."; this.loading = false; return; } if (this.signInState instanceof MfaVerificationRequiredState) { const result = await this.signInState.submitChallenge(this.mfaChallenge); if (result.isFailed()) { if (result.error?.isIncorrectChallenge()) { this.error = "Incorrect code."; } else { this.error = result.error?.errorData?.errorDescription || "An error occurred while verifying the challenge response."; } } if (result.isCompleted()) { this.isSignedIn = true; this.userData = result.data; this.showMfaChallenge = false; this.signInState = result.state; } } this.loading = false; }Update the /src/app/components/sign-in/sign-in.component.html files to display the correct MFA challenge forms (MFA challenge method selection or MFA challenge method verification):
<!-- Use shared MFA auth method selection form --> <app-mfa-auth-method-selection-form *ngIf="showMfaAuthMethods" [authMethods]="mfaAuthMethods" [selectedAuthMethod]="selectedMfaAuthMethod" [loading]="loading" (selectedAuthMethodChange)="selectedMfaAuthMethod = $event" (submitForm)="submitMfaAuthMethod()"> </app-mfa-auth-method-selection-form> <!-- Use shared MFA challenge form --> <app-mfa-challenge-form *ngIf="showMfaChallenge" [challenge]="mfaChallenge" [loading]="loading" (challengeChange)="mfaChallenge = $event" (submitForm)="submitMfaChallenge()"> </app-mfa-challenge-form>
Handle multifactor authentication after sign-up or password reset
MFA flow after sign-up and password reset works similar to the MFA in the sign-in flow. After a successful sign-up or password reset, the SDK can automatically continue with the sign-in flow. If the user has a strong authentication method registered, the flow transitions to MFA challenge verification.
Handle multifactor authentication after sign-up
For MFA flow after sign-up, you need you update the /src/app/components/sign-up/sign-up.component.ts file. See the complete code in sign-up.component.ts:
Make sure you import the required types and components.
Handle MFA requirements states in a similar manner as it happens in the sign-in flow; after sign-up completes successfully, use the result to automatically trigger a sign-in flow as shown in the following code snippet:
// In your sign-up completion handler if (this.signUpState instanceof SignUpCompletedState) { const result = await this.signUpState.signIn(); ... if (result.isMfaRequired()) { this.showMfaAuthMethods = true; this.showPassword = false; this.showCode = false; this.showAuthMethodsForRegistration = false; this.showChallengeForRegistration = false; this.showMfaChallenge = false; this.mfaAuthMethods = result.state.getAuthMethods(); // Set default selection to the first MFA auth method this.selectedMfaAuthMethod = this.mfaAuthMethods.length > 0 ? this.mfaAuthMethods[0] : undefined; this.signUpState = result.state; } ... }Update your /src/app/components/sign-up/sign-up.component.html file to add the MFA forms (MFA method selection form and MFA challenge verification form). See a complete example in sign-up.component.html.
Handle multifactor authentication after password reset
For MFA flow after SSPR, you need you update the /src/app/components/reset-password/reset-password.component.ts file. See the complete code in reset-password.component.ts:
Make sure you import the required types and components.
Handle MFA requirements states in a similar manner as it happens in the sign-in flow; after sign-up completes successfully, use the result to automatically trigger a sign-in flow as shown in the following code snippet:
if (this.resetState instanceof ResetPasswordCompletedState) { const result = await this.resetState.signIn(); ... if (result.isMfaRequired()) { this.showMfaAuthMethods = true; this.showCode = false; this.showNewPassword = false; this.showAuthMethodsForRegistration = false; this.showChallengeForRegistration = false; this.showMfaChallenge = false; this.isReset = false; this.mfaAuthMethods = result.state.getAuthMethods(); // Set default selection to the first MFA auth method this.selectedMfaAuthMethod = this.mfaAuthMethods.length > 0 ? this.mfaAuthMethods[0] : undefined; this.resetState = result.state; } ... }Update your /src/app/components/reset-password/reset-password.component.html file to add the MFA forms (MFA method selection form and MFA challenge verification form). See a complete example in reset-password.component.html.
Run and test your app
Before you test your app, make sure you have a user account that has a registered strong authentication method. Use the steps in Run and test your app to run your app, but this time, test the MFA flow.