react-native-app-auth: On some Android devices, the application crashes after passing the authentication procedure

Issue

On some Android devices, the application crashes after passing the authentication procedure. Works correctly on most devices.

Config:

const { AUTH_CLIENT_ID, AUTH_CLIENT_SECRET, AUTH_REALM, AUTH_URL, AUTH_APP_SCHEME } = Config;

export function getAuthConfig(): AuthConfiguration {
    return {
        issuer: `${AUTH_URL}/realms/${AUTH_REALM}`,
        clientId: AUTH_CLIENT_ID,
        clientSecret: AUTH_CLIENT_SECRET || undefined,
        redirectUrl: `${AUTH_APP_SCHEME}:/callback`,
        scopes: ['openid'],
    };
}

Error:

Caught exception
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Boolean.booleanValue()' on a null object reference
 at com.rnappauth.RNAppAuthModule.onActivityResult(RNAppAuthModule.java:398)
 at com.facebook.react.bridge.ReactContext.onActivityResult(ReactContext.java:308)
 at com.facebook.react.ReactInstanceManager.onActivityResult(ReactInstanceManager.java:758)
 at com.facebook.react.ReactDelegate.onActivityResult(ReactDelegate.java:90)
 at com.facebook.react.ReactActivityDelegate.onActivityResult(ReactActivityDelegate.java:112)
 at com.facebook.react.ReactActivity.onActivityResult(ReactActivity.java:68)
 at android.app.Activity.dispatchActivityResult(Activity.java:7454)
 at android.app.ActivityThread.deliverResults(ActivityThread.java:4375)
 at android.app.ActivityThread.handleSendResult(ActivityThread.java:4424)
 at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
 at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
 at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1818)
 at android.os.Handler.dispatchMessage(Handler.java:106)
 at android.os.Looper.loop(Looper.java:193)
 at android.app.ActivityThread.main(ActivityThread.java:6762)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Environment

  • Your Identity Provider: Keycloak
  • Platform that you’re experiencing the issue on: Android
  • Are you using Expo?: no

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 4
  • Comments: 20 (2 by maintainers)

Most upvoted comments

Has anybody found a work around for this yet?

Still facing this issue on latest version of react-native-app-auth

"react-native": "0.66.2",
"react-native-app-auth": "6.4.0",

I’ve also been experiencing this error. The strange thing is - I’m explicitly passing skipCodeExchange as true. As I’m not a Java developer - this has me scratching my head a bit…

I don’t see how this line 419 can be throwing an error considering this method invocation from the RN side:

export const authorize = ({
  issuer,
  redirectUrl,
  clientId,
  clientSecret,
  scopes,
  useNonce = true,
  usePKCE = true,
  additionalParameters,
  serviceConfiguration,
  clientAuthMethod = 'basic',
  dangerouslyAllowInsecureHttpRequests = false,
  customHeaders,
  additionalHeaders,
  skipCodeExchange = false,
}) => {
  validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
  validateClientId(clientId);
  validateRedirectUrl(redirectUrl);
  validateHeaders(customHeaders);
  validateAdditionalHeaders(additionalHeaders);
  // TODO: validateAdditionalParameters

  const nativeMethodArguments = [
    issuer,
    redirectUrl,
    clientId,
    clientSecret,
    scopes,
    additionalParameters,
    serviceConfiguration,
    skipCodeExchange,
  ];

  if (Platform.OS === 'android') {
    nativeMethodArguments.push(useNonce);
    nativeMethodArguments.push(usePKCE);
    nativeMethodArguments.push(clientAuthMethod);
    nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
    nativeMethodArguments.push(customHeaders);
  }

  if (Platform.OS === 'ios') {
    nativeMethodArguments.push(additionalHeaders);
    nativeMethodArguments.push(useNonce);
    nativeMethodArguments.push(usePKCE);
  }

  return RNAppAuth.authorize(...nativeMethodArguments);
};

And this method definition on the native side:

@ReactMethod
    public void authorize(
            String issuer,
            final String redirectUrl,
            final String clientId,
            final String clientSecret,
            final ReadableArray scopes,
            final ReadableMap additionalParameters,
            final ReadableMap serviceConfiguration,
            final Boolean skipCodeExchange,
            final Boolean useNonce,
            final Boolean usePKCE,
            final String clientAuthMethod,
            final boolean dangerouslyAllowInsecureHttpRequests,
            final ReadableMap headers,
            final Promise promise
    ) {
        this.parseHeaderMap(headers);
        final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.authorizationRequestHeaders);
        final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests);
        final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);

        // store args in private fields for later use in onActivityResult handler
        this.promise = promise;
        this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;
        this.additionalParametersMap = additionalParametersMap;
        this.clientSecret = clientSecret;
        this.clientAuthMethod = clientAuthMethod;
        this.skipCodeExchange = skipCodeExchange;
        this.useNonce = useNonce;
        this.usePKCE = usePKCE;

On the native side, skipCodeExchange is only ever set in the one location.

… Unless there is more than one instance of RNAppAuthModule on the native side? So when public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) is called when the OAuth browser activity completes - the skipCodeExchange field could be null depending on which instance is being called?

I guess this is also what @mishann is implying above:

Perhaps the system closes the application during authentication, and after a redirect, the object is reinitialized with null parameters. It may be worthwhile to store the config in the repository.

I redid the work with authorization using WebView through my own solution, everything works

@mishann can you elaborate at all on this? 🙏