Implement a 'Password Reset' with JavaScript and Azure AD B2C
Tags: security, azure, javascript
Published: 2020-12-15 02:28:42

Azure AD B2C provides a lot of great functionality for dealing with user sign up/sign in and profile management out of the box without the need to write any code. In this blog post I'll show you how to implement "Password Reset" in a (vanilla) JavaScript web app. 

# Create the Azure AD B2C App Registration
First, we'll register and configure an Azure AD B2C app registration so that we can authenticate and manage users in our JavaScript application. 

Open the App Registrations blade in Azure AD B2C and create a new app registration

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nk8jq8u26m02p418958x.png)

Provide a meaningful name and leave everything else default before pressing the **Register** button

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ric55julnh6ubvzatczg.png)

Now we can add the **Authentication** settings we need for our application:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/v16b4ekdreh3m3yd6gm0.png)

And since we're working with a simple JS application, we need to choose **Single Page Application**

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ty7aodhczbcw3gatliy5.png)

On the next tab we only need to add a **Redirect URI**. We don't need to worry about Implicit Flow anymore since [MSAL.js v2.x](https://developer.microsoft.com/en-us/microsoft-365/blogs/msal-js-2-0-supports-authorization-code-flow-is-now-generally-available/) supports PKCE out of the box :), in effect, making our JavaScript applications more secure!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f4eb9os6klp9tz9wefxq.png)

# Configure the User Flows
User flows in B2C dictate how users interact with our application. They define experiences such as Sign In/Sign Up, Edit profile etc etc. For this blog post we need a combined **SignIn/SignUp** and an **Password Reset** policy. 

The first is absolutely mandatory, the other ones are optional to implement but highly recommended if you want a smooth user experience. Let's get to it.

Back at the root of the B2C blade, open the **User Flows** blade.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jmfschj5p9ntg35xtk4z.png)

First, we'll create our Sign In/Sign Up policy. Press the **New User Flow** button and select **Sign up and sign in** and **Version -> Recommended**

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/19r8cuu574zhhvdzkoj9.png)

Next we need to provide some details, starting with the name of the policy (has to be unique in our tenant), configure Local Accounts to use email signup and finally the attributes we want to collect and return in our token claims. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4xvu71wnpocz5361o80a.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3gz06t6n0vrdoing0mg7.png)

Press **OK** for the user attributes and **Create** in the user flow.

This concludes the configuration of the Sign In/Sign Up flow. 

> Bonus tip: You can easily test a User Flow directly from the AAD B2C portal by adding `jwt.ms` as a Redirect URI to any app registration 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/npkqeio34r5f68x5n6y5.png)

The last step in the AD B2C portal is to configure the Password Reset flow. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/k3zv5nhx6yuu1q5th0ge.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5hfkcvh4nyyhulvgnjke.png)

This concludes all the changes we need to do on the Azure AD B2C portal to accommodate users that need to sign in and reset their credentials.

# Writing the code
My code is loosely based on this [AAD B2C JS sample](https://github.com/Azure-Samples/ms-identity-b2c-javascript-spa) but significantly simplified to have bare minimum for the purpose of this blog post

The main MSAL.js code is in the `authPopup.js`. The rest of the code deals with config settings (`authConfig.js`) or UI changes in response to user actions (`ui.js`)

```
const myMSALObj = new msal.PublicClientApplication(msalConfig);

let accountId = "";
let username = "";

function selectAccount () {
    const currentAccounts = myMSALObj.getAllAccounts();

    if (currentAccounts.length === 0) {
        return;
    } else if (currentAccounts.length > 1) {
        // Add your account choosing logic here
        console.log("Multiple accounts detected.");
    } else if (currentAccounts.length === 1) {
        accountId = currentAccounts[0].homeAccountId;
        username = currentAccounts[0].username;
        welcomeUser(username);
    }
}

selectAccount();

function handleResponse(response) {
    if (response !== null) {
        accountId = response.account.homeAccountId;
        username = response.account.username;
        welcomeUser(username);
    } else {
        selectAccount();
    }
}

function signIn() {
    myMSALObj.loginPopup(loginRequest)
        .then(handleResponse)
        .catch(error => {
            console.log(error);
            if (error.errorMessage) {
                if (error.errorMessage.indexOf("AADB2C90118") > -1) {
                    myMSALObj.loginPopup(b2cPolicies.authorities.forgotPassword)
                        .then(response => handlePolicyChange(response));
                }
            }
        });
}

function signOut() {
    const logoutRequest = {
        account: myMSALObj.getAccountByHomeId(accountId)
    };
    
    myMSALObj.logout(logoutRequest).then(goodbyUser);
}

function editProfile() {
    myMSALObj.loginPopup(b2cPolicies.authorities.editProfile)
      .then(response => handlePolicyChange(response));
}

function handlePolicyChange(response) {
    if (response.idTokenClaims['acr'] === b2cPolicies.names.editProfile) {
        window.alert("Profile has been updated successfully. \nPlease sign-in again.");
        myMSALObj.logout();
    } else if (response.idTokenClaims['acr'] === b2cPolicies.names.forgotPassword) {
        window.alert("Password has been reset successfully. \nPlease sign-in with your new password.");
        myMSALObj.logout();
    }
}
```

As you can see, there's not much code to implement to wire up authentication in our JavaScript app using Azure AD B2C. 

## Password Reset with AAD B2C
The MSAL.js library has a specific way for dealing with password resets. Password resets manifest as exceptions in the login/signin process. When users are presented with the sign in popup/redirect, they have the option to execute a password reset. The password reset throws an exception that developers need to catch and and handle accordingly. The code below shows how the password reset event is captured in code where the users are redirected to the Reset Password flow:

```
function signIn() {
    myMSALObj.loginPopup(loginRequest)
        .then(handleResponse)
        .catch(error => {
            console.log(error);
            if (error.errorMessage) {
                if (error.errorMessage.indexOf("AADB2C90118") > -1) {
                    myMSALObj.loginPopup(b2cPolicies.authorities.forgotPassword)
                        .then(response => handlePolicyChange(response));
                }
            }
        });
}
```
> If you want to know more about AAD code, check out the documentation [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/398vz98ckdzva3bneenr.gif)

## Source code.
If you want to see the solution working end-to-end, clone this [GitHub repo](https://github.com/cmatskas/ms-identity-b2c-javascript-spa) run it locally.

## Summary
As you can see, implementing the password reset functionality with B2C requires 2 steps:
- Step 1: Configure the appropriate user flow in the B2C portal
- Step 2: Extend the SignIn method in your code to capture the AAD error thrown due to the user initiating the Password Reset and calling the appropriate user flow

I hope this is helpful but as always, feel free to let us know if you have any other questions or want to us to blog or stream something that you find challenging.