Skip to content

ComposeAuth: failed Supabase token exchange on iOS Google sign-in crashes the app instead of routing to NativeSignInResult.Error #99

Description

@whoamimjg

Summary

On iOS, rememberSignInWithGoogle runs onIdToken.invoke(...) (which calls auth.signInWith(IDToken)) inside an unguarded scope.launch on the Main dispatcher. When Supabase rejects the token (e.g. BadRequestRestException — "Unacceptable audience in id_token"), the exception escapes the coroutine → kotlinx.coroutines final-resort → processUnhandledExceptionthe app aborts. It never reaches the onResult = NativeSignInResult.Error callback, so apps cannot show an error for a failed Google sign-in on iOS.

Affected code

ComposeAuth/src/appleMain/.../composable/GoogleAuth.kt — the signInCompletion completion callback, else if (idToken != null) branch. Present through 3.6.0 / current main.

} else if (idToken != null) {
    logger.d { "Id token available" }
    onIdToken.invoke(/* ... */)   // throws here (e.g. BadRequest) -> escapes the Main coroutine -> abort
    onResult.invoke(NativeSignInResult.Success(SignInResultData.Google()))
}

Why Android and Apple don't crash (the asymmetry is the bug)

  • Android parseCredential wraps signInWithGoogle(idToken) in try/catch → onResult.Error.
  • iOS Apple (AppleAuth.kt, ASAuthorizationController delegate didCompleteWithAuthorization) wraps onIdToken.invoke in try/catch → onResult.Error.
  • iOS Google is the only path with no guard around onIdToken.invoke.

Reproduce

An iOS app using rememberSignInWithGoogle where the native flow returns a valid Google ID token but the Supabase Google provider's configured client ID does not match the serverClientId audience → BadRequest → the app aborts instead of delivering NativeSignInResult.Error. Confirmed live with the "Unacceptable audience in id_token" error.

Proposed fix

Mirror the Apple delegate's handling — wrap the sign-in + success in try/catch:

                 } else if (idToken != null) {
                     logger.d { "Id token available" }
-                    onIdToken.invoke(
-                        composeAuth = this@rememberSignInWithGoogle,
-                        result = IdTokenCallback.Result(
-                            idToken = idToken,
-                            provider = Google,
-                            nonce = startedStatus.nonce,
-                            extraData = startedStatus.extraData
-                        )
-                    )
-                    onResult.invoke(NativeSignInResult.Success(SignInResultData.Google()))
+                    try {
+                        onIdToken.invoke(
+                            composeAuth = this@rememberSignInWithGoogle,
+                            result = IdTokenCallback.Result(
+                                idToken = idToken,
+                                provider = Google,
+                                nonce = startedStatus.nonce,
+                                extraData = startedStatus.extraData
+                            )
+                        )
+                        onResult.invoke(NativeSignInResult.Success(SignInResultData.Google()))
+                    } catch (e: Exception) {
+                        coroutineContext.ensureActive()
+                        logger.e(e) { "Error logging into Supabase with Google ID Token" }
+                        onResult.invoke(NativeSignInResult.Error(e.message ?: "error"))
+                    }
                 }

Happy to open a PR if this looks right.

Environment

  • supabase-kt 3.3.0 (also verified the bug is present on current main = 3.6.0)
  • Compose Multiplatform on iOS (iosSimulatorArm64 / iosArm64), Kotlin/Native

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions