🔐 Part 4: Token Theft via SharedPreferences — How JWTs Leak from Flutter Apps

🔐 Part 4: Token Theft via SharedPreferences — How JWTs Leak from Flutter Apps

FlutterPulse

This article was translated specially for the channel FlutterPulseYou'll find lots of interesting things related to Flutter on this channel. Don't hesitate to subscribe!🚀

Welcome back to the Flutter App Hacking & Security series.
 In this part, we explore a highly common yet overlooked vulnerability —…

🔙 Check Previous Part [How Dev Builds Can Open the Door to Attackers]

Welcome back to the Flutter App Hacking & Security series.
In this part, we explore a highly common yet overlooked vulnerability — storing sensitive tokens like JWTs or OAuth credentials in SharedPreferences.

These tokens are meant to represent authenticated sessions — but when you store them insecurely, attackers can extract them, reuse them, or even impersonate users without ever cracking a password.

Let's break it down.

🚨 What's the Problem?

Many Flutter apps store sensitive tokens like this:

final prefs = await SharedPreferences.getInstance();
prefs.setString('auth_token', jwt);

But SharedPreferences is just a plain XML file stored on internal device storage. On rooted devices, emulators, or with physical access — it's easy to read, modify, or exfiltrate.

🕵️‍♂️ Real-World Attack Scenario

Goal:
Extract an active JWT token and reuse it in an API call — gaining unauthorized access.

Tools Needed:

  • Rooted Android device or emulator
  • adb (Android Debug Bridge)
  • curl or Postman

🛠️ Step-by-Step Exploit

  1. Install and open the target Flutter app. Login once.

This writes the token into FlutterSharedPreferences.xml.

  • Access device storage via adb:
adb shell
run-as com.example.app
cd shared_prefs
cat FlutterSharedPreferences.xml
  • Extract the token:
<string name="flutter.auth_token">eyJhbGciOiJIUzI1NiIsInR5cCI6...</string>
  • Replay the token in an API call:
curl -H "Authorization: Bearer eyJ..." https://api.example.com/user/profile

You now have full access — no MFA, no password, just the raw token.

📉 Real-World Impact

Risk Level: CRITICAL

🛡️ How to Fix It

✅ Fix 1: Use Encrypted Storage

Replace SharedPreferences with flutter_secure_storage:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();
await storage.write(key: 'auth_token', value: jwt);

This encrypts data using Android Keystore or iOS Keychain.

✅ Fix 2: Set Expiry & Refresh Strategy

Never keep tokens "forever" on the device. Instead:

  • Store short-lived access tokens (e.g., 15 mins)
  • Refresh tokens via a secure endpoint
  • Invalidate server-side tokens when app uninstalls/logs out

✅ Fix 3: Implement Backend Token Revocation & Rotation

Even if a token is stolen, backend should detect unusual behavior:

  • Device mismatch?
  • IP address change?
  • Expired or inactive token?

Force logout and rotate tokens accordingly.

✅ Fix 4: Harden the Device Environment

At minimum:

  • Detect root or emulator using packages like device_info_plus
  • Limit token exposure in logs, stack traces, or debug tools
  • Clear secure storage on logout, uninstall, or suspicious behavior

❌ Anti-Patterns to Avoid

✅ Developer Checklist

  • Never store tokens in SharedPreferences
  • Use encrypted storage like flutter_secure_storage
  • Store only short-lived access tokens
  • Refresh and revoke tokens securely
  • Detect and handle device manipulation (root, emulator)
  • Monitor backend for suspicious session reuse

🔐 Bonus: Detect If App Is Running on Rooted Device

import 'package:root_check/root_check.dart';

bool isRooted = await RootCheck.isDeviceRooted;
if (isRooted) {
// Block access or restrict features
}

👀 Up Next

🚨 Part 5: JavaScript Injection in Flutter WebViews — How Untrusted Web Content Can Run Dart Code

Thank you for reading this article

If I missed something or made an error, please let me know in the comments. I'm always eager to learn and improve.

Give a clap 👏 if you found this article helpful.

Report Page