Skip to content

GT-3079 Migrate Google authentication to Android Credential Manager - #4656

Open
smgoss wants to merge 2 commits into
developfrom
fix/issue-4612
Open

smgoss wants to merge 2 commits into
developfrom
fix/issue-4612

Conversation

@smgoss

@smgoss smgoss commented Sep 28, 2026

Copy link
Copy Markdown

Resolves #4612 (GT-3079)

GoogleAccountProvider still used the legacy GoogleSignIn APIs that were removed in play-services-auth 22, which blocks the dependency updates in #4575 and #4582. This moves Google login to Credential Manager, following the same provider shape as FacebookAccountProvider.

Changes

  • GoogleAccountProvider:
    • Interactive login (Login and Create Account) gets a credential with GetSignInWithGoogleOption using the Activity context from LocalContext. It then exchanges the Google ID token through the existing AuthApi (AuthToken.Request(googleIdToken = ...)), so the server contract does not change.
    • Silent re-authentication (authenticateWithMobileContentApi(), used by MobileContentApiSessionInterceptor) tries the stored ID token first. If the token is missing or the API rejects it, it gets a new credential with GetGoogleIdOption (authorized accounts only, auto-select). It only accepts that credential if it is for the same account.
    • Credential Manager has nothing like getLastSignedInAccount, so the account id, the latest ID token and the GodTools user id are saved in the provider's private SharedPreferences (org.godtools.account.google). userId, isAuthenticated and the flows now read only from those prefs, so they still work after process recreation.
    • Failures map to the existing result model. Cancellation, no credential, an unexpected credential type and ID token parsing errors all become MissingCredentials. A failed or mismatched silent refresh becomes UnableToRefreshCredentials. Network errors (IOException) are passed through as before, and API errors go through extractAuthToken().
    • logout() calls clearCredentialState() (a ClearCredentialException is logged at .d) and then clears the local prefs.
    • ID tokens are never logged. Only exceptions are logged, and expected failures use .d.
  • GoogleModule: provides CredentialManager, GetSignInWithGoogleOption and GetGoogleIdOption from GoogleBuildConfig.serverClientId. These replace GoogleSignInOptions and GoogleSignInClient.
  • Added androidx-credentials, androidx-credentials-play-services-auth (shared androidx-credentials version) and googleid to the version catalog and :library:account.
  • Added an R8 consumer rule for the Credential Manager Play Services provider (library/account/src/main/proguard-credentials.pro).
  • Removed play-auth (play-services-auth), gtoSupport-play-auth and kotlin-coroutines-play-services from :library:account and the catalog. These are orphaned by the migration and are removed in a separate commit.

Intentionally not changed: the AccountProvider.rememberLauncherForLogin() API and the login UI. The Google provider returns an ActivityResultLauncher<AccountType> that runs the suspend login on the composition's coroutine scope, the same way GodToolsAccountManager wraps its launchers. So LoginLayout and the Circuit login work in #4610 are not affected.

Behavior changes to be aware of:

  • Existing Google users are signed out once after upgrading. The GodTools user id is now stored under a new pref key, and there is no stored Credential Manager account yet. The old user_id_<googleId> pref entries are left unused in the same prefs file, and the next logout clears them.
  • Silent refresh now goes through Credential Manager instead of silentSignIn(). With auto-select and one authorized account this should need no user input, but Credential Manager may show its "signing in" UI. Because this runs from the API interceptor, it uses the application context. This needs a manual check on a device (see Tests).
  • Update dependency com.google.android.gms:play-services-auth to v22 #4575 and Bump com.google.android.gms:play-services-auth from 21.6.0 to 22.0.0 #4582 no longer apply, because play-services-auth is no longer a direct dependency. It now comes in transitively through credentials-play-services-auth.

Dependency versions (please verify)

Google Maven (dl.google.com) is blocked in the environment where this was written, so no Gradle resolution was possible.

  • androidx.credentials:credentials / credentials-play-services-auth 1.6.0: taken from the official AndroidX release notes page (latest stable, April 8, 2026). Not resolved locally.
  • com.google.android.libraries.identity.googleid:googleid 1.2.1: NOT verified from an official source. The Google release notes page was unreachable. 1.2.0 is widely used, and 1.2.1 only showed up in third-party Renovate/Dependabot PRs from September 2026. If 1.2.1 does not resolve, fall back to 1.2.0.
  • R8: :library:account now ships a consumer rule (src/main/proguard-credentials.pro) that keeps androidx.credentials.playservices.**. The Sign in with Google guide asks for this because CredentialManager loads the Play Services provider by reflection. If the 1.6.0 artifact already ships the same rule, this one is a harmless duplicate. A release or QA build should still be used to check Google sign-in.

Tests

  • GoogleAccountProviderTest was rewritten around mocked CredentialManager and GoogleIdTokenCredential.createFrom(). It covers:
    • properties and flows backed by prefs, including persistence across provider instances
    • login(): success, cancellation, no credentials, unexpected credential type, parsing failure, network error, API rejection
    • logout(): clears Credential Manager and local state, and still clears local state when clearCredentialState() fails
    • authenticateWithMobileContentApi(): stored token success, network error, not authenticated, missing token refresh, rejected token refresh, refresh failure, refresh for a different account
  • Not compiled or run locally. Gradle cannot reach its Maven repositories in the authoring environment, so ./gradlew :library:account:test, ./gradlew :build-logic:ktlintCheck ktlintCheck, ./gradlew lint and ./gradlew bundle still need to pass in CI. Every Credential Manager and googleid API used was checked against the official Sign in with Google implementation guide, not against the resolved artifacts.
  • Manual check still needed: Google Login, Create Account, logout, and an expired-token API call (silent refresh) on a device with a release-like build.

Review

Checked against the project conventions, a correctness review, a security review and a simplification pass:

  • Dagger graph: GoogleAccountProvider needs AuthApi, the app Context, CredentialManager, GetSignInWithGoogleOption and GetGoogleIdOption. All are provided. GoogleBuildConfig comes from app/.../dagger/AccountModule.kt. No @TestInstallIn module or other code still refers to GoogleSignInClient or GoogleSignInOptions.
  • Version catalog: googleid is declared inline because only one artifact uses it, like play-base. The two credentials artifacts share a version.ref. Both follow the repo's pattern and are in alphabetical order. Neither version could be resolved here (Google Maven is blocked).
  • No references to GoogleSignIn, gms.auth or coroutines-play-services remain. The login button string comes from play-services-base, which :app depends on directly.
  • Changed: added the R8 consumer rule.
  • No security findings. ID tokens are never logged. A refreshed credential is accepted only for the stored account.
  • No migration was added for existing Google users. Issue Migrate Google authentication to Android Credential Manager #4612 requires them to sign in once more. Carrying over only the old user_id_<sub> value would be unsafe. Those users have no stored account id and no ID token. Once their API session expires, authenticateWithMobileContentApi() would fail, and the app would still show them as logged in. Fixing that would mean weakening the same-account check on refresh.
  • Still open, only settled on a device: the silent refresh calls getCredential() with the application context. It runs from the OkHttp interceptor inside runBlocking, often while the app is in the background. It may fail, or show UI, where silentSignIn() never did. If it fails, the user stays logged in but API calls go without auth until they sign in again.

Generated by Claude Code

Stephen Goss added 2 commits September 27, 2026 21:42
The legacy GoogleSignIn APIs are removed in play-services-auth 22,
which blocks the pending dependency updates. GoogleAccountProvider now
gets Google ID tokens from Credential Manager instead:

- Interactive login uses GetSignInWithGoogleOption and exchanges the ID
  token with the existing AuthApi.
- Silent re-authentication uses GetGoogleIdOption limited to authorized
  accounts, and only accepts a refreshed credential for the same
  account.
- Credential Manager has no getLastSignedInAccount equivalent, so the
  account id, ID token and GodTools user id are persisted in the
  provider's private SharedPreferences. The user id now lives under a
  new key, so existing Google users sign in again once.
- Logout calls clearCredentialState() and clears the local state.

The AccountProvider launcher API is unchanged; the Google provider
returns a launcher that runs the suspend login on the composition scope.
The tests now mock CredentialManager and GoogleIdTokenCredential
parsing.

CredentialManager loads its Play Services provider by reflection, so
:library:account now ships a consumer R8 rule that keeps
androidx.credentials.playservices, as the Sign in with Google guide
asks. Without it, R8 strips the provider from release and QA builds and
Google sign-in fails.
GoogleAccountProvider no longer uses GoogleSignIn, GoogleSignInKtx or
the Task await() helpers, so play-services-auth, gto-support-play-auth
and kotlinx-coroutines-play-services are no longer needed by
:library:account. They had no other users, so their catalog entries go
too.
@codecov

codecov Bot commented Sep 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.42105% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.70%. Comparing base (c118367) to head (32a8a14).

Files with missing lines Patch % Lines
...s/account/provider/google/GoogleAccountProvider.kt 78.00% 8 Missing and 3 partials ⚠️
...u/godtools/account/provider/google/GoogleModule.kt 0.00% 7 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4656      +/-   ##
===========================================
+ Coverage    53.56%   53.70%   +0.13%     
===========================================
  Files          440      440              
  Lines        11572    11594      +22     
  Branches      1944     1946       +2     
===========================================
+ Hits          6199     6227      +28     
+ Misses        4790     4787       -3     
+ Partials       583      580       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@smgoss
smgoss marked this pull request as ready for review September 28, 2026 20:20
@smgoss
smgoss requested review from a team and frett September 28, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Google authentication to Android Credential Manager

1 participant