How to use MSAL with React?

Viewed 5512

I feel I might be close, but I can't get the msal.js library to work with react.

After logging in via Microsoft I get redirected to my callback but with the code in the URL, e.g.

http://localhost:3000/authcallback#code=0.AQsAuJTIrioCF0ambVF28BQibk37J9vZQ05FkNq4OB...etc

The interaction.status key in local storage is stuck on interaction_in_progress

I'm using the redirect flow and I suspect there is something wrong with how I'm registering the required callback.

Here is my wrapper class AuthService.ts

export class AuthService {
    msalClient: PublicClientApplication;
    loginRequest: RedirectRequest;
    account: AccountInfo | null;

    constructor(configuration: Configuration, loginScope: string) {
        this.msalClient = new PublicClientApplication(configuration);
        this.loginRequest = {
            scopes: [loginScope, "openid", "profile"],
        };
        this.account = null;
    }

    loadAuthModule(): void {
        this.msalClient
            .handleRedirectPromise()
            .then((resp: AuthenticationResult | null) => {
                if (resp) {
                    this.handleResponse(resp);
                }
            })
            .catch(console.error);
    }

    handleResponse(response: AuthenticationResult) {
        if (response !== null) {
            store.dispatch(signIn(response.account));
        }
    }

    signIn(): void {
        this.msalClient.loginRedirect(this.loginRequest);
    }

    signOut(): void {
        this.msalClient.logout();
    }

    async getIdentity(): Promise<AuthenticationResult | null> {
        // Stuff
    }
}

Here is my config wrapper class

export class AuthConfig {
    configuration: Configuration;
    loginScope: string;

    constructor(clientId: string, authority: string, redirectUrl: string, loginScope: string) {
        this.loginScope = loginScope;
        this.configuration = {
            auth: {
                clientId: clientId,
                authority: authority,
                redirectUri: redirectUrl,
                navigateToLoginRequestUrl: false,
            },
            cache: {
                cacheLocation: "localStorage",
                storeAuthStateInCookie: false,
            },
            system: {
                loggerOptions: {
                    loggerCallback: (level: any, message: any, containsPii: any) => {
                        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;
                        }
                    },
                },
            },
        };
    }
}

Then in my login page which is the first page on the site...

export let authService = {} as AuthService;

const LoginPage: React.FC<RouteComponentProps> = ({}) => {

    useEffect(() => {
        const config = new AuthConfig(
            (window as any).ENV.CLIENT_ID,
            (window as any).ENV.AUTHORITY,
            (window as any).ENV.REDIRECT_URL,
            (window as any).ENV.LOGIN_SCOPE
        );

        authService = new AuthService(config.configuration, config.loginScope);  
        authService.loadAuthModule();    
    }, [])

    const signIn = () => {
        authService.signIn();
    };

    return (    
        <Button onClick={() => signIn()}>Sign In</Button>          
    );
};

export default LoginPage;

My config details are correct. I assume I wouldn't get as far as I can without them being correct.

Where am I going wrong here?

Every example I have seen has hard coded values for the config but this doesn't seem very secure, hence attempting to inject them from my config.js file. I'm open to suggestions on how to do this whilst maintaining a globally accessible msal instance.

2 Answers

I can definitely see an argument about maintainability for pushing this out to config, but you should not consider it a security measure. If it's available to client-side javascript, it's available for inspection in dev tools.

Related