v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: HIGH auth.kotlin.android.token-in-sharedprefs

An auth token / secret is written to plain SharedPreferences (prefs.edit().putString("auth_token", ...)).

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

SharedPreferences is an unencrypted XML file in the app's private storage: on a rooted device, via a device backup, or through any local-file exposure it is read in the clear, leaking the credential (CWE-312). AI-generated Android samples reach for plain SharedPreferences because it is the "hello world" of persistence and never switch to encrypted storage.

Store credentials in EncryptedSharedPreferences (androidx.security.crypto) backed by the Android Keystore instead: val prefs = EncryptedSharedPreferences.create( context, "secure_prefs", masterKey, AES256_SIV, AES256_GCM) prefs.edit().putString("auth_token", token).apply()

VULNERABLE
vulnerable.kt
import android.content.Context

class TokenStore(context: Context) {
    private val prefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    fun save(token: String) {
        // ruleid: auth.kotlin.android.token-in-sharedprefs
        prefs.edit().putString("auth_token", token).apply()
    }

    fun saveSecret(secret: String) {
        // ruleid: auth.kotlin.android.token-in-sharedprefs
        prefs.edit { putString("client_secret", secret) }
    }
}
SAFE
safe.kt
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences

class TokenStore(context: Context) {
    private val plainPrefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    fun saveEncrypted(context: Context, token: String) {
        val prefs = EncryptedSharedPreferences.create(
            context, "secure_prefs", masterKey, AES256_SIV, AES256_GCM)
        // encrypted storage: safe
        prefs.edit().putString("auth_token", token).apply()
    }

    fun saveUsername(name: String) {
        // non-credential key: safe
        plainPrefs.edit().putString("username", name).apply()
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.kotlin.android.token-in-sharedprefs -- <reason>

References

https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences ↗https://cwe.mitre.org/data/definitions/312.html ↗