{"record":{"id":"7c13e0c710acc6ff","repo":"microg/GmsCore","slug":"failed-to-obtain-login-token","errorCode":null,"errorMessage":"Failed to obtain login token.","messagePattern":"Failed to obtain login token\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"vending-app/src/main/java/org/microg/vending/billing/InAppBillingServiceImpl.kt","lineNumber":169,"sourceCode":"                createIAPCore(\n                    context,\n                    buyFlowCacheEntry.account,\n                    buyFlowCacheEntry.packageName\n                ).requestAuthProofToken(password)\n            }\n            return runBlocking { deferred.await() }\n        }\n\n        private fun createIAPCore(context: Context, account: Account, pkgName: String): IAPCore {\n            val key = \"$pkgName:${account.name}\"\n            val cacheEntry = iapCoreCacheMap[key]\n            if (cacheEntry != null) {\n                if (cacheEntry.expiredAt > System.currentTimeMillis())\n                    return cacheEntry.iapCore\n                iapCoreCacheMap.remove(key)\n            }\n            val authData = AuthManager.getAuthData(context, account)\n                ?: throw RuntimeException(\"Failed to obtain login token.\")\n            val deviceEnvInfo = createDeviceEnvInfo(context)\n                ?: throw RuntimeException(\"Failed to retrieve device information.\")\n            val clientInfo = createClient(context, pkgName)\n                ?: throw RuntimeException(\"Failed to retrieve client information.\")\n            val iapCore = IAPCore(context.applicationContext, deviceEnvInfo, clientInfo, authData)\n            iapCoreCacheMap[key] =\n                IAPCoreCacheEntry(iapCore, System.currentTimeMillis() + EXPIRE_MS)\n            return iapCore\n        }\n    }\n\n    private fun getPreferredAccount(extraParams: Bundle?): Account {\n        val name = extraParams?.getString(\"accountName\")\n        name?.let {\n            extraParams.remove(\"accountName\")\n        }\n        return getGoogleAccount(context, name)\n            ?: throw RuntimeException(\"No Google account found.\")","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/vending-app/src/main/java/org/microg/vending/billing/InAppBillingServiceImpl.kt#L151-L187","documentation":"createIAPCore builds an IAPCore using auth data from AuthManager.getAuthData; when it returns null (no login token for the account) this RuntimeException is thrown. It means the device has no usable Google auth token for the given account, so IAP API calls cannot be authenticated.","triggerScenarios":"Any billing call (acquireRequest, getPurchaseHistory, consumePurchaseExtraParams, getSkuDetailsExtraParams, acknowledgePurchase, requestAuthProofToken) that goes through createIAPCore while AuthManager.getAuthData(context, account) returns null — no cached/refreshable auth token.","commonSituations":"Device not signed in or account removed; auth token refresh failing due to network/auth server issues; microG not permitted to fetch auth tokens; account credentials invalidated (password change, security events).","solutions":["Ensure the device has a signed-in Google account and the app targets the correct account name.","Trigger account re-authentication (re-add account or refresh credentials) so AuthManager can obtain a token.","Check network connectivity and microG auth component health; retry after token refresh succeeds.","Catch this RuntimeException in the billing path and surface a 'sign-in required' state to the user."],"exampleFix":"// before\nval history = billing.getPurchaseHistory(api, pkg, type, extras)\n// after\ntry {\n    val history = billing.getPurchaseHistory(api, pkg, type, extras)\n} catch (e: RuntimeException) {\n    if (e.message == \"Failed to obtain login token.\") promptSignIn()\n    else throw e\n}","handlingStrategy":"try-catch","validationCode":"val accounts = AccountManager.get(context).getAccountsByType(\"com.google\")\nif (accounts.isEmpty()) promptSignIn() else proceedBilling()","typeGuard":"fun hasGoogleAuth(account: Account?, context: Context): Boolean =\n    account != null && AuthManager.getAuthData(context, account) != null","tryCatchPattern":"try {\n    billing.getPurchaseHistory(...)\n} catch (e: RuntimeException) {\n    if (e.message?.contains(\"login token\") == true) promptReAuth()\n    else throw e\n}","preventionTips":["Verify a signed-in Google account exists before billing calls","Handle token refresh failures with re-authentication","Monitor network state; retry auth after connectivity returns","Surface sign-in-required states instead of crashing"],"tags":["android","authentication","in-app-billing","microg"],"backgroundTag":"missing-credentials","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}