Search code examples
reactjsazureazure-web-app-serviceazure-ad-b2cazure-ad-msal

React msal-browser not working after deploy on App Service


I created react app integrated with Azure AD B2C using "msal-browser" and "msal-react". If user is not logged in, app redirect to AD authorization, if user logged in, app let user to use entire application.

Every thing works locally very well. Problem was arrived when I deploy app to azure app service.

After open app I correctly redirect to login page. After login I fall into an infinity loop. Url keeps changing from https://domain.azurewebsites.net/ to https://domain.azurewebsites.net/#state=[...] and again to https://domain.azurewebsites.net/ and again to https://domain.azurewebsites.net/#state=[...] etc.

In dev console i got error: POST

https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{signUpSignInPolicies}/oauth2/v2.0/token 400 (Bad Request)

Here is my code: App.tsx:

function App() {
  return (
      <MainRoutes />
  );
}

export default withAuthHOC(App);

withAuthHOC.tsx:

const withAuthHOC = (WrappedComponent: React.FunctionComponent) => {
    return () => {
        const pca = new PublicClientApplication(msalConfig);
        useMsalRedirect();

        return (
            <MsalProvider instance={pca}>
                <AuthenticatedValidation />
                <AuthenticatedTemplate>
                    <WrappedComponent />
                </AuthenticatedTemplate>
            </MsalProvider>
        );
    }
};

export default withAuthHOC;

useMsalRedirect.tsx:

const useMsalRedirect = () => {
    const { instance } = useMsal();

    useEffect(() => {
        const callbackId = instance.addEventCallback((event: any) => {
            if (event.eventType === EventType.LOGIN_FAILURE) {
                if (event.error && event.error.errorMessage.indexOf("AADB2C90118") > -1) {
                    if (event.interactionType === InteractionType.Redirect) {
                        instance.loginRedirect(b2cPolicies.authorities.forgotPassword as RedirectRequest);
                    } else if (event.interactionType === InteractionType.Popup) {
                        instance.loginPopup(b2cPolicies.authorities.forgotPassword as RedirectRequest)
                            .catch(e => {
                                return;
                            });
                    }
                }
            }

            if (event.eventType === EventType.LOGIN_SUCCESS || event.eventType === EventType.ACQUIRE_TOKEN_SUCCESS) {
                if (event?.payload) {
                    if (event.payload.idTokenClaims["tfp"] === b2cPolicies.names.forgotPassword) {
                        window.alert("Password has been reset successfully. \nPlease sign-in with your new password.");
                        return instance.logout();
                    } else if (event.payload.idTokenClaims["tfp"] === b2cPolicies.names.editProfile) {
                        window.alert("Profile has been edited successfully. \nPlease sign-in again.");
                        return instance.logout();
                    }
                }
            }
        });

        return () => {
            if (callbackId) {
                instance.removeEventCallback(callbackId);
            }
        };
    }, [instance]);
}

export default useMsalRedirect;

AuthenticatedValidation.tsx:

export const AuthenticatedValidation = () => {
    const isAuthenticated = useIsAuthenticated();
    const { instance, inProgress } = useMsal();
    useEffect(() => {
        if (inProgress === InteractionStatus.None && !isAuthenticated) {
            instance.loginRedirect(loginRequest);
        }
    });

    return null;
}

export default AuthenticatedValidation;

authConfig.js

import { LogLevel } from "@azure/msal-browser";

export const b2cPolicies = {
    names: {
        signUpSignIn: "B2C_1A_signup_signin",
        forgotPassword: "B2C_1_passwordreset",
        editProfile: "B2C_1_profileediting"
    },
    authorities: {
        signUpSignIn: {
            authority: "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/B2C_1A_signup_signin",
        },
        forgotPassword: {
            authority: "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/B2C_1_passwordreset",
        },
        editProfile: {
            authority: "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/B2C_1_profileediting"
        }
    },
    authorityDomain: "{tenant}.b2clogin.com"
}

export const msalConfig = {
    auth: {
        clientId: "{clientId}",
        authority: b2cPolicies.authorities.signUpSignIn.authority,
        knownAuthorities: [b2cPolicies.authorityDomain],
        redirectUri: "https://domain.azurewebsites.net/", //localhost: "/"
        postLogoutRedirectUri: "https://domain.azurewebsites.net/", //localhost: "/"
        navigateToLoginRequestUrl: false,
    },
    cache: {
        cacheLocation: "sessionStorage",
        storeAuthStateInCookie: false,
    },
    system: {
        loggerOptions: {
            loggerCallback: (level, message, containsPii) => {
                if (containsPii) {
                    return;
                }
                switch (level) {
                    case LogLevel.Error:
                        console.error(message);
                        return;
                    case LogLevel.Info:
                        console.info(message);
                        return;
                    case LogLevel.Verbose:
                        console.debug(message);
                        return;
                    case LogLevel.Warning:
                        console.warn(message);
                        return;
                    default:
                        return;
                }
            }
        }
    }
};

export const loginRequest = {
    scopes: ['openid', 'profile'],
};

I deployment the application using visual studio code. My app service is Node 12 LTS with Startup Command: pm2 serve /home/site/wwwroot/build --no-daemon --spa

In Azure AD B2C Properties i have:

Inculde web app/web API: Yes
Allow implict flow: yes
Replay URLs and redirect URIs: https://domain.azurewebsites.net/ and https://localhost:3000
App ID URI: https://{tenant}.onmicrosoft.com/{clientId}
Include native client: yes

My signUpSignIn policy is created using Identity Experience Framework.

The application works fine on localhost. After changing export default with Auth HOC (App); to export default App;. The application works fine on azure (with authorization disabled). Therefore, I think the problem is with the configuration of the authConfig.js file. Anyone have an idea what I'm doing wrong?


Solution

  • I solved the problem by moving instantiate PublicClientApplication outside a react component - to index tsx