· 5 min read · Nadeera Silva
Taking Over an Existing Flutter App and PHP Backend: My Playbook
How I take over a live Flutter app with a legacy PHP backend: a first-week checklist, reproducing bugs, PHP security fixes and a safe move to token auth.

Some of my freelance work starts with an app that's already in production. On Recipians, I took over a client's live Flutter app with a PHP and MySQL backend and an admin panel. The work covered debugging across all three parts, security hardening, token-based authentication and in-app purchase billing. If you're about to take over an existing Flutter app or a legacy PHP codebase, this is the playbook I'd follow, with the checks and code I'd start with.
Week one: change nothing
The first job is to understand the system, not improve it. Real users depend on it, and the original developer may not be around.
| Check | Why it matters |
|---|---|
| Source for the app, backend and admin panel matches what's deployed | You can't safely fix code that isn't what's running |
| Who holds App Store Connect, Play Console, domain, hosting and database access | A release you can't publish is a release you can't fix |
| How backend code reaches the server | Stops a "quick fix" overwriting someone's unrecorded change |
| A database and file backup that you've actually restored | A backup you've never restored is a guess |
| A map of which endpoints the app calls and what the admin panel can change | Answers "what will this break?" |
On the Flutter side, a few commands tell you a lot in ten minutes:
flutter --version # compare with the version the project was built on
flutter pub get
flutter analyze # how much has drifted
flutter pub outdated # how far behind the dependencies are
flutter build apk --release # can it still build at all?If the release build fails, that's your first task, before any bug. You need to be able to ship a fix.

Reproduce bugs before fixing them
Client reports describe symptoms. "Payments sometimes fail" could be an app bug, an API bug, a database problem or a third-party outage. For each issue:
- Identify which part fails: the app, the API or the admin panel.
- Check server and error logs around the time it happened.
- Reproduce it on a test build or a staging copy.
- Fix it and confirm the original steps no longer fail.
Fixing a bug you can't reproduce is guessing. Sometimes that's necessary, but it should be the exception.
Legacy PHP security: where to look first
Older PHP and MySQL backends tend to share the same risks. This is the order I review them in.
SQL injection. Any query that joins request data into a string should become a prepared statement. OWASP's cheat sheet lists prepared statements as the first defence:
// Before: injectable
$rows = $db->query("SELECT * FROM orders WHERE user_id = " . $_GET['user_id']);
// After: PDO prepared statement
$stmt = $pdo->prepare('SELECT * FROM orders WHERE user_id = ?');
$stmt->execute([$userId]);
$rows = $stmt->fetchAll();Password storage. Passwords should go through password_hash, not plain text or a fast hash like MD5. You don't need a forced reset to migrate; upgrade each user's hash the next time they sign in:
if (password_verify($password, $user['password_hash'])) {
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
// save $newHash for this user
}
// signed in
}For legacy MD5 hashes, check the old hash once at sign-in, then replace it with a password_hash value straight away.
Authorization on every endpoint. Each endpoint should check that the caller may access the record they asked for. Hiding a button in the app isn't access control.
Secrets in code. API keys and database passwords belong in server configuration, not the repository or the app bundle. I explain why build-time tricks don't hide keys in how to hide API keys in Flutter.
Admin panel. Strong authentication, not easy to find, and protected against brute force.
Errors and input. Validate input on the server, and make sure errors never print stack traces or SQL to users.
Moving a live app to token-based authentication
Adding token-based authentication was part of the Recipians work. If an API relies on something weaker, such as the app sending a user ID with each request, it can't tell who's really calling.
With tokens, the user signs in once, the server issues a token, and the app sends it with every request. The server checks it first and derives the user from it, instead of trusting an ID from the request.
Rolling this out to a live app needs a transition plan:
- Older app versions that users haven't updated will still call the API the old way.
- Decide how long the old method keeps working, and when to require the update.
- Make the app handle an expired token by asking the user to sign in again, not by failing silently.
- Store tokens on the device with
flutter_secure_storage, not plain shared preferences.
Adding billing to an existing app
Adding in-app purchases to an app with existing users touches live account data. Keep it additive: new tables or fields for purchases and subscription status, existing user records left alone. Verify purchases on the server as you would in a new app (my StoreKit subscriptions post covers the server side), and keep billing out of the admin panel's general edit screens so access can't be changed by accident.
Keep the client informed
After the first week, you'll often know more about the internals than the client does. Short, regular updates help:
- What I found, in plain language.
- What I fixed, and how I tested it.
- What's still risky, and what I'd recommend next.
Clear updates build trust and turn a pile of fixes into a plan. If the Flutter side's state management is part of the mess, my Riverpod best practices post shows the structure I migrate towards.
You can read about the project on the Recipians case study page.
Frequently asked questions
What should I do first when taking over an existing app?
Change nothing. Get access to every account and repository, confirm the code matches production, take a backup and restore it once, and map how the parts talk to each other.
How do I secure a legacy PHP API?
Move string-built SQL to prepared statements, hash passwords with password_hash, check authorization on every endpoint, move secrets to server config and stop errors leaking details.
How do I migrate old password hashes in PHP?
Upgrade them at sign-in: after password_verify succeeds, use password_needs_rehash and save a new password_hash value. For MD5 hashes, check the old hash once and replace it straight away.
How do I add token authentication to a live app?
Issue a token at sign-in and derive the user from it on every request. Keep the old method working for older app versions for a planned period, then require the update.
What if the inherited Flutter app doesn't build?
Make the release build work first. Until you can ship, you can't deliver any fix.
Sources
- SQL Injection Prevention Cheat Sheet (OWASP)
- Prepared statements and stored procedures (PDO) (PHP)
- password_hash (PHP)
- password_needs_rehash (PHP)
- password_verify (PHP)
- flutter_secure_storage (pub.dev)
Keep reading
- StoreKit 2 Subscriptions in Flutter: Restore Purchases and Server NotificationsStoreKit 2 subscriptions in a Flutter app: server verification, App Store Server Notifications V2, restore purchases and one access record across gateways.
- How to Hide API Keys in Flutter (and What to Do Instead)You can't hide an API key inside a Flutter app. Which keys are safe to ship, how to restrict them, and how to keep real secrets behind a proxy with App Check.
- Flutter UIScene Migration: Fix iOS 27 Launch CrashesFlutter apps built with Xcode 27 quit at launch without the UIScene lifecycle. Here's how to migrate a custom AppDelegate, Info.plist and plugins.
Comments
Share a question or note about this article.
Loading comments…