Skip to content
All articles

9 min read

Fix "[28444] Developer console is not set up correctly" in Flutter Google Sign-In

Flutter google_sign_in 7 fails with "[28444] Developer console is not set up correctly" or "serverClientId must be provided"? What is missing and how to fix it.

By Yevhen Beshkarov

  • troubleshooting
  • android
  • firebase

Since version 7, google_sign_in signs users in on Android through Credential Manager, and a broken setup shows up as one of a few messages. The best known is [28444] Developer console is not set up correctly, and the checklists for it usually start with the SHA-1. I reproduced each case on an emulator, and with current Google Play services a missing SHA-1 doesn't give the app this error at all. The app gets a canceled with Account reauth failed instead. 28444 showed up when the app sent the wrong client ID.

Everything below was checked on an Android 17 emulator with Google Play services 26.41, google_sign_in 7.2.0 and google_sign_in_android 7.2.18, against a Firebase project. Before version 7, the plugin reported a broken setup as ApiException: 10, the DEVELOPER_ERROR of Google Play services.

What each Google Sign-In error means

GoogleSignIn.instance.authenticate() throws a GoogleSignInException. The code and the description tell the cases apart:

DescriptionCodeWhat is wrong
[28444] Developer console is not set up correctly.unknownErrorGoogle doesn't take the serverClientId as the web client of the app: it is the ID of the Android client, or a client that doesn't exist
serverClientId must be provided on AndroidclientConfigurationErrorThe app has no web client ID at all: google-services.json has none, or the release build dropped it
[16] Account reauth failed.canceledNo Android OAuth client with the package name of the app and the SHA-1 of the key that signed it, or no connection to Google
[16] Cancelled by user.canceledThe user closed the sheet, or backed out of adding a Google account

The two cases of canceled share a code. The README of the plugin warns about this: it can't tell some setup errors from a user who cancelled. attemptLightweightAuthentication() hides a missing SHA-1 completely and just returns null.

logcat prints GetCredentialResponse error returned from framework for every failure, a plain cancel included, so that line doesn't point to the cause. And on a device or an emulator without a Google account, the sign-in never gets far enough to fail: Credential Manager opens the screen that adds an account. Add one before you test the setup.

See the real cause in logcat

Google Play services logs what its server answered. Filter logcat to its two tags while you try to sign in:

Shell
adb logcat -s Auth:W Auth.Api.Credentials:W

For a missing SHA-1, the first lines are:

Text
W Auth    : [GetTokenResponseHandler] Server returned error: This android application is not registered to use OAuth2.0, please confirm the package name and SHA-1 certificate fingerprint match what you registered in Google Developer Console.
W Auth.Api.Credentials: cwsp: [8] Unknown error [status=UNREGISTERED_ON_API_CONSOLE].
W Auth.Api.Credentials: cwsp: [16] Account reauth failed.

For a wrong client ID, the same tags show 28444: Developer console is not set up correctly.

[28444] Developer console is not set up correctly: the wrong client ID

To give the app an ID token, Google needs the client ID of a web application OAuth client, which google_sign_in sends as serverClientId. When Google doesn't accept that ID, the app gets:

Text
GoogleSignInException(code GoogleSignInExceptionCode.unknownError, [28444] Developer console is not set up correctly., null)

I got it in two ways. The first was passing the ID of the Android client to initialize(): a Firebase project has both kinds, and they look alike, <number>-<letters>.apps.googleusercontent.com. The second was an ID that doesn't exist, as with a typo or a client deleted in the Google Cloud console.

In google-services.json, the web client is the one with "client_type": 3, and the Android clients have "client_type": 1. To print the web client ID:

Shell
grep -B1 '"client_type": 3' android/app/google-services.json | grep -m1 client_id

If you pass the ID yourself, pass that one:

Dart
await GoogleSignIn.instance.initialize(
  serverClientId: '1234567890-abc123.apps.googleusercontent.com',
);

With a google-services.json that has a web client, you don't need to pass anything: the plugin reads the ID from the resources that the Google services Gradle plugin generates from that file.

The checklists aren't wrong to mention the SHA-1, though. Inside Google Play services, a missing SHA-1 is 28444 too: logcat shows cwsp: [28444] when attemptLightweightAuthentication() runs into one, even though the app only gets null. I can't rule out that other versions of Play services pass it on to the app. If the client ID is right, check the SHA-1 next.

Account reauth failed: the SHA-1 of the signing key is missing

Google recognizes an Android app by its package name and the SHA-1 of the certificate that signed it. With Firebase, adding a SHA fingerprint to the Android app creates the Android OAuth client that Google looks for. When there is none, the user picks an account and the app gets:

Text
GoogleSignInException(code GoogleSignInExceptionCode.canceled, [16] Account reauth failed., null)

The same message shows up in two other cases. When the device can't reach Google (on the emulator I turned off Wi-Fi and mobile data), logcat shows [7] Network error. right before it. And in flutter/flutter#190296, an app with a correct setup got it on every sign-in after the first one. The reporter fixed it by calling GoogleSignIn.instance.signOut() on sign-out. I tried that on the same emulator: eight sign-ins in a row without signOut(), some right after the app's own sign-out, and all of them worked. So it may depend on something that report had and my setup didn't, such as a physical device, an older plugin or an app installed from Google Play. logcat tells the cases apart: a missing SHA-1 shows not registered to use OAuth2.0, and no network shows Network error.

Find the SHA-1 of the build that fails

Gradle prints the key that signs each build type, and it is not always the one you expect:

Shell
cd android && ./gradlew signingReport
Text
Variant: release
Config: debug
Store: /Users/you/.android/debug.keystore
Alias: AndroidDebugKey
SHA1: 60:45:3E:2E:7F:82:01:EC:F4:84:F8:43:A2:44:70:F9:4D:F9:7B:F5

A new Flutter app signs its release build with the debug key too (signingConfig = signingConfigs.getByName("debug") in android/app/build.gradle.kts), until you give it a key of its own. From then on, the release needs that key's SHA-1.

For an APK you already have, read the certificate from the file with apksigner from the Android SDK build tools:

Shell
$ANDROID_HOME/build-tools/<version>/apksigner verify --print-certs build/app/outputs/flutter-apk/app-release.apk

keytool -printcert -jarfile app.apk from Google's client authentication guide prints Not a signed jar file for a Flutter APK. Flutter sets minSdk to 24, and from that level on, the APK has only a v2 signature, which keytool doesn't read.

Some keys are easy to forget. Every machine has its own ~/.android/debug.keystore, so a teammate's debug build has a different SHA-1, and so does a CI runner that creates a fresh key. Google Play adds two more: your upload key, and the app signing key. With Play App Signing, users install a build that Google signed with its own key, and its SHA-1 is in the Play Console, under Test and release > App integrity > App signing.

Add the SHA-1 to Firebase

In the Firebase console, open Project settings, pick the Android app under Your apps and click Add fingerprint. Add the SHA-256 too. The Firebase CLI does the same, with the app ID from firebase apps:list ANDROID:

Shell
firebase apps:android:sha:create <appId> <sha1>

You don't need to download google-services.json again for this: the app never reads the Android clients from it. In my test, the same build signed in about 40 seconds after I added the SHA-1, with the old file.

Every SHA you add creates another Android OAuth client in the project, and Google caps the number of them. Removing a SHA from Firebase doesn't remove its client. Once the project is full, apps:android:sha:create fails with Failed to create SHA certificate hash, and only --debug shows the answer of the API: 429 Number of OAuth clients created exceeded limit. I ran into that because the CI of SMF registered the fresh debug key of every runner. Give CI one debug keystore that doesn't change, and register its SHA-1 once.

serverClientId must be provided on Android: no web client in google-services.json

When you don't pass serverClientId, google_sign_in_android reads the string resource default_web_client_id. The Google services Gradle plugin generates it from the web client in android/app/google-services.json. Without it, authenticate() fails at once, before any Google screen:

Text
GoogleSignInException(code GoogleSignInExceptionCode.clientConfigurationError, serverClientId must be provided on Android, null)

The usual cause is a google-services.json downloaded before Google sign-in was enabled in Firebase Authentication. That file has "oauth_client": [], and the build gets no default_web_client_id. Check your file:

Shell
grep -c '"client_type": 3' android/app/google-services.json

0 means no web client. Enable Google in the Firebase console, under Authentication > Sign-in method, then download the file again, from Project settings or with the Firebase CLI:

Shell
firebase apps:sdkconfig ANDROID <appId> --out android/app/google-services.json

Also check that android/app/build.gradle.kts applies the com.google.gms.google-services plugin, which FlutterFire CLI adds when it configures the app. Without it, nothing generates the resource.

Works in debug, fails in release

When Google Sign-In works in debug builds and fails in the release, the message tells you which of two things happened.

If the release fails with [16] Account reauth failed., Firebase doesn't know the key of the release: your release key, or, for a build from Google Play, the app signing key of Google Play. Add its SHA-1 as above.

If it fails with serverClientId must be provided on Android, the release build lost default_web_client_id. Flutter shrinks the resources of a release build by default, and the plugin looks the string up by its name at run time, which the shrinker doesn't see. Up to google_sign_in_android 7.2.15, the shrinker removed it from every app (flutter/flutter#188363). Version 7.2.16 added a keep rule in res/raw/keep.xml of the plugin (flutter/packages#12075).

That rule has a catch (flutter/flutter#194943, open as I write this, with two pull requests that rename the plugin's file under review). If your app has its own android/app/src/main/res/raw/keep.xml, for example to keep a notification icon, Gradle merges the resources by file name, and the app's file replaces the plugin's. With 7.2.18, the release build then fails with serverClientId must be provided on Android. The shrinker says why:

Shell
grep default_web_client_id build/app/outputs/mapping/release/resources.txt
Text
string:default_web_client_id:2131689529 is not reachable.

Rename your file, for example to res/raw/app_keep.xml. The Android guide to keeping resources asks for unique names for exactly this reason, and the shrinker reads every keep file in res/raw. After the rename, the same line says reachable from keep xml file, and sign-in works. You can also add @string/default_web_client_id to the tools:keep of your own file. Either way, check that the app has 7.2.16 or later:

Shell
flutter pub deps | grep google_sign_in_android

A CI that builds only debug never sees either case. The same thing happened with the shared_preferences release build crash, where R8 broke a plugin only in release builds, so start a release build on an emulator before your users do.

How SMF sets up Google Sign-In with Firebase

I went through all of this while building the google_sign_in module of SMF, a free, open-source Flutter project generator. In an app with the firebase_auth module, SMF generates a script, tool/enable_firebase_sign_in.dart, and runs it after flutterfire configure once you agree. The script enables Google in the Firebase project, registers the SHA-1 and SHA-256 of the debug key of your machine, downloads google-services.json again with the web client in it, and writes the URL scheme of the iOS app. So a debug build signs in with Google from the first run, and a teammate runs the script once on their own machine. The keys of a release stay a step you do by hand, with the commands in the README of the app.

Back to all articles

Release notes by email

Get an email when SMF or its modules have a new release.

We use Resend. You can unsubscribe at any time.