Nanfeng

Notes on software development, code, and curious ideas

Implementing Google Web Sign-In on Android

When an Android app uses Google’s web OAuth flow, authentication must run in an external user agent such as a browser or Custom Tab—not an embedded WebView. After authorization, a registered redirect URI returns control to the app.

Flow overview

  1. Create an OAuth client in Google Cloud and configure the consent screen.
  2. Generate a cryptographically random state value and a PKCE verifier/challenge.
  3. Open the authorization endpoint in a browser or Custom Tab.
  4. Receive the redirect through an Android App Link or registered custom scheme.
  5. Validate state, exchange the authorization code on a trusted backend or through a standards-compliant native OAuth client, and verify the resulting tokens.

Do not pass an ID token or authorization result through an unverified arbitrary URL. Never embed a client secret in an Android package; installed apps cannot keep one confidential.

Declare the redirect

For a custom-scheme callback, add an intent filter to the receiving activity. Replace the values with the redirect registered for the OAuth client:

1
2
3
4
5
6
7
8
9
10
11
12
<activity
android:name=".OAuthRedirectActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="com.example.app"
android:host="oauth2redirect" />
</intent-filter>
</activity>

Verified HTTPS App Links provide stronger ownership guarantees when a domain is available.

Receive the result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val uri = intent.data ?: return
val code = uri.getQueryParameter("code")
val returnedState = uri.getQueryParameter("state")
val error = uri.getQueryParameter("error")

if (error != null) {
showLoginError(error)
return
}
if (returnedState != pendingState || code == null) {
showLoginError("Invalid OAuth response")
return
}
completeCodeExchange(code, pendingPkceVerifier)
}

Use a maintained OAuth/OIDC library where possible; it handles Custom Tabs, PKCE, redirect dispatch, and protocol edge cases. Store only the minimum session data, transmit it over HTTPS, and let the server create its own application session after token validation.

The original web page can still initiate login, but the Android app should receive only a standards-based, validated callback—not credentials assembled by page JavaScript and placed directly in a deep link.

+