David Miguel
David Miguel

Reputation: 14470

How does Firebase Authentication Android SDK persist the refresh token?

I was reading through the docs but I could not find how the SDK persists the refresh token on the device to be able to keep the user authenticated when the app is killed.

What I would like to find out is:

Anyone has any insights about it?

Upvotes: 6

Views: 1040

Answers (1)

David Miguel
David Miguel

Reputation: 14470

After creating a small demo app integrating Firebase Auth and inspecting it, I think I found what I was looking for:

  • Where is the refresh token stored? In SharedPreferences
  • How? In plain text

Details:

Firebase Auth uses SharedPreferences to cache all information relative to the authenticated Firebase User.

Data stored in a SharedPreferences object is written to a plain-text XML file in the app's internal storage directory. The file can be found in:

/data/data/{app_package_name}/shared_prefs/com.google.firebase.auth.api.Store.{hash}.xml

The file contains two key-value pairs, both of them contain the refresh token:

  • com.google.firebase.auth.FIREBASE_USER:
{
  "cachedTokenState": "{\"refresh_token\":\"AIwUa...\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"issued_at\":1651836746042}",
  "applicationName": "[DEFAULT]",
  "type": "com.google.firebase.auth.internal.DefaultFirebaseUser",
  "userInfos": [
    "{\"userId\":\"sDa2XJk...\",\"providerId\":\"firebase\",\"email\":\"[email protected]\",\"isEmailVerified\":false}",
    "{\"userId\":\"[email protected]\",\"providerId\":\"password\",\"email\":\"[email protected]\",\"isEmailVerified\":false}"
  ],
  "anonymous": false,
  "version": "2",
  "userMetadata": {
    "lastSignInTimestamp": 1651836746972,
    "creationTimestamp": 1651579404681
  }
}
  • com.google.firebase.auth.GET_TOKEN_RESPONSE.{hash}:
{
  "refresh_token": "AIwUa...",
  "access_token": "eyJhb...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "issued_at": 1651836746042
}

Security implications:

The app's internal storage directory where the SharedPreferences file is stored is not accessible by other apps in non-rooted devices and it is encrypted in devices with Android 10 or higher (as file-based encryption is now mandatory).

Android's Auto-Backup automatically backs up the app's internal storage directory to Google Drive on devices running Android 6 or higher. The backup is end-to-end encrypted on devices running Android 9 or higher using the device's pin, pattern, or password.

Based on the above, the refresh token could be compromised (at least) in the following scenarios:

  • In a rooted device, an app with root access could read the SharedPreferences XML file where the refresh token is stored.
  • In a non-rooted device with Android < 10 and full-disk or file-based encryption not enabled, a malicious actor with physical access to the device could mount the disk and read the refresh token.
  • When creating a backup using adb backup, encrypting the backup file is optional. If the backup is not encrypted and a malicious actor gains access to the backup file, he could extract the refresh token from it.
  • In a non-rooted device with Android ≥ 6 and < 9 and with Auto-Backup enabled for the app, the backup file stored in Google Drive is not end-to-end encrypted. So a malicious actor that gained access to your Google Account could access the backup file and consequently the refresh token.

Potential mitigations measures:

  • Add root detection to warn the user about its risks (or assume that a user with a rooted phone is already aware of them).
  • Recommend users to enable encryption if not enabled.
  • Exclude Firebase Authentication SharedPreferences file from backups.

But if you need to fully comply with OWASP MSTG-STORAGE-1 rule:

System credential storage facilities need to be used to store sensitive data, such as PII, user credentials or cryptographic keys.

Then your only option would be to use the Firebase Authentication REST API instead of using their SDK and persist the refresh token yourself using a proper encrypted storage like EncryptedSharedPreferences.

Otherwise you can consider some alternative providers that support encryption in their SDKs by default, like:

This test was performed against Firebase Auth v21.0.3 which was the latest at the time of writing.


Update: I found this statement from the Firebase team regarding their approach:

As for the named SharedPreferences file, com.google.firebase.auth.api.Store.{code}.xml, that is, indeed, where we persist user state. When the application goes into the background or is closed, the currently logged-in user needs to be stored somewhere, and SharedPreferences is a safe place to do so - it has private filesystem permissions, meaning that only that application (or a process with root permissions) can read it. As such, encryption isn't necessary. In general, Firebase only makes security guarantees about unrooted, non-userdebug devices; the security properties of rooted or userdebug devices are significantly degraded in ways that we cannot properly account for.

Still, the question remains, why don't we encrypt things in storage? The answer is pretty simple: it provides no additional security (other than security-by-obscurity) and has both a performance and complexity penalty. For example, a root user can also access the Android Keystore as the target application, meaning that they can still read the SharedPreferences, they just have to read and use the appropriate key first.

Upvotes: 8

Related Questions