Search code examples
react-nativeexpomsal

expo react-native-msal [TypeError: Cannot read property 'createPublicClientApplication' of null]


I'm having a similar issue as the question here: Cannot read property 'createPublicClientApplication' of null in react-native-msal

But I want to provide more insight as I have already tried the suggestions.

Project used with

Guides

Expo Setup: https://github.com/stashenergy/react-native-msal/blob/master/docs/expo_setup.md

Follow step1 of android setup for expo: https://github.com/stashenergy/react-native-msal/blob/master/docs/android_setup.md

(optional) tried to follow the guide from this article, but it seems to have some missing pieces in it: https://medium.com/@tarunnarula/securing-react-native-msal-app-with-cross-validation-using-python-cecb8087881a

Setup

Using android studio... enter image description here

enter image description here

notice the package name and build successful in android studio enter image description here

using the commandline... enter image description here

gave the sha1 key output and the package name(com.ced.expomsaldemo) to our azure admin. He returned the following info...

{
  "client_id" : "<-- client uuid -->",
  "authorization_user_agent" : "DEFAULT",
  "redirect_uri" : "msauth://com.example.expomsaldemo/<-- hash key -->",
  "authorities" : [
    {
      "type": "AAD",
      "audience": {
        "type": "AzureADMyOrg",
        "tenant_id": "<-- tenant uuid -->"
      }
    }
  ]
}

The Code

enter image description here

import React, { useState } from 'react';
import { Pressable, Text, View } from 'react-native';
import PublicClientApplication from 'react-native-msal';
import type { MSALConfiguration, MSALInteractiveParams, MSALResult } from 'react-native-msal';
import { useNavigation } from '@react-navigation/native';
import { mainScreenSelections } from '@app/utils';
import { buttonStyles, globalStyles } from '@app/styles';

const config: MSALConfiguration = {
  auth: {
    clientId: '<-- client uuid from azure -->',
    // This authority is used as the default in `acquireToken` and `acquireTokenSilent` if not provided to those methods.
    // Defaults to 'https://login.microsoftonline.com/common'
    authority: `https://login.microsoftonline.com/<-- tenant uuid from azure -->/v2.0`
  }
};
const scopes = ['scope1', 'scope2'];

export default function Guest(): JSX.Element {
  const navigation = useNavigation();
  const [isLoggingIn, setIsLoggingIn] = useState(false);

  const handleLogin = async () => {
    try {
      setIsLoggingIn(true);
      const pca = new PublicClientApplication(config);
      const response = await pca.init();
      console.log('response:', response);

      // Acquiring a token for the first time, you must call pca.acquireToken
      const acquireTokenParams: MSALInteractiveParams = { scopes };
      console.log('acquireTokenParams:', acquireTokenParams);
      const acquireTokenResult: MSALResult | undefined = await pca.acquireToken(acquireTokenParams);
      console.log('acquireTokenResult:', acquireTokenResult);

      // On subsequent token acquisitions, you can call `pca.acquireTokenSilent`
      // Force the token to refresh with the `forceRefresh` option
      // const params: MSALSilentParams = {
      //   account: result!.account, // or get this by filtering the result from `pca.getAccounts` (see below)
      //   scopes,
      //   forceRefresh: true
      // };
      // const result: MSALResult | undefined = await pca.acquireTokenSilent(params);

      // // Get all accounts for which this application has refresh tokens
      // const accounts: MSALAccount[] = await pca.getAccounts();

      // // Retrieve the account matching the identifier
      // const account: MSALAccount | undefined = await pca.getAccount(result!.account.identifier);

      // // Remove all tokens from the cache for this application for the provided account
      // const success: boolean = await pca.removeAccount(result!.account);

      // // Same as `pca.removeAccount` with the exception that, if called on iOS with the `signoutFromBrowser` option set to true, it will additionally remove the account from the system browser
      // const params: MSALSignoutParams = {
      //   account: result!.account,
      //   signoutFromBrowser: true
      // };
      // const success: boolean = await pca.signOut(params);
    } catch (error: any) {
      console.log('error:', error);
    } finally {
      setIsLoggingIn(false);
    }
  };

  return (
    <View style={{ ...globalStyles.guestContainer, gap: 20 }}>
      <Text>Guest Screen</Text>
      <Pressable
        style={buttonStyles.btnMain}
        onPress={() => navigation.navigate(mainScreenSelections.dashboard.route as never)}
      >
        <Text style={buttonStyles.btnMainTxt}>Go To Dashboard</Text>
      </Pressable>
      <Pressable style={buttonStyles.btnMain} onPress={handleLogin}>
        <Text style={buttonStyles.btnMainTxt}>Login</Text>
      </Pressable>
      {isLoggingIn && <Text>Logging In...</Text>}
    </View>
  );
}

Steps to reproduce issue

enter image description here

press 'a' on your keyboard

Login enter image description here

See error: enter image description here


Solution

  • I don't think this is a solution, however this is what I did to fix it...

    1. go to <project_location>\node_modules\react-native-msal\android\build.gradle and replace the following code snippet...
    android {
      compileSdkVersion safeExtGet('Msal_compileSdkVersion', 33)
      defaultConfig {
        minSdkVersion safeExtGet('Msal_minSdkVersion', 16)
        targetSdkVersion safeExtGet('Msal_targetSdkVersion', 29)
        versionCode 1
        versionName "1.0"
      }
    
      buildTypes {
        release {
          minifyEnabled false
        }
      }
      lintOptions {
        disable 'GradleCompatible'
      }
      compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
      }
    }
    
    1. In the <project_location>\app.json add the following...
    {
      "expo": {
        // ...
        "plugins": [
          [
            "expo-build-properties",
            {
              "android": {
                "minSdkVersion": 23,
                "compileSdkVersion": 33,
                "targetSdkVersion": 33,
                "buildToolsVersion": "33.0.0"
              },
              "ios": {
                "deploymentTarget": "13.0"
              }
            }
          ]
        ]
      }
    }
    

    Again, I don't think switching version numbers in the node_modules is a maintainable solution, so imo I think they still need to work on making this backwards compatable, as there are other libraries I also have the same issue with where I have to use specific sdk versions that differ from version 33.