Reputation: 14470
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
Reputation: 14470
After creating a small demo app integrating Firebase Auth and inspecting it, I think I found what I was looking for:
SharedPreferences
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:
SharedPreferences
XML file where the refresh token is stored.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.Potential mitigations measures:
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