[{"data":1,"prerenderedAt":3012},["ShallowReactive",2],{"\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026\u002F":3,"\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026\u002F-related":374,"\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026\u002F-resources":2996},{"id":4,"title":5,"author":6,"body":10,"category":310,"createdAt":311,"description":312,"disclosure":313,"extension":314,"faq":315,"howTo":313,"location":334,"meta":335,"navigation":341,"ogImage":313,"path":342,"primaryKeyword":343,"readingTime":344,"related":345,"seo":349,"sources":350,"stem":364,"tags":365,"takeaways":366,"updatedAt":311,"url":372,"__hash__":373},"blog\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026.md","SafetyNet Is Gone: Moving to Play Integrity and App Check",{"name":7,"social":8},"Simon Dziak",{"twitter":9},"@SzymonDziak",{"type":11,"value":12,"toc":299},"minimark",[13,21,24,30,35,107,112,124,127,149,152,156,159,204,208,217,277,281],[14,15,16,20],"p",{},[17,18,19],"strong",{},"SafetyNet Attestation is gone: Google turned it down fully in January 2025, so any app or backend that still relies on it gets an error on every check."," Replace it with the Play Integrity API. If you used SafetyNet through Firebase App Check, switch App Check to its Play Integrity provider. For App Check users the code change is small, and most of the work is setup in Play Console and the Firebase console.",[14,22,23],{},"If you landed here from an old link to Google's SafetyNet attestation guide, that address now redirects to the deprecation timeline. Dates and behavior come from Android Developers and Firebase documentation, as listed in the sources. I checked each page on October 18, 2026.",[14,25,26,29],{},[17,27,28],{},"Disclosure:"," App369 is my studio. I build and fix Flutter and Firebase apps, and the last section mentions what I sell. Google doesn't pay me for anything on this page.",[31,32,34],"h2",{"id":33},"what-happened-to-safetynet-and-when","What happened to SafetyNet, and when",[36,37,38,54],"table",{},[39,40,41],"thead",{},[42,43,44,48,51],"tr",{},[45,46,47],"th",{},"When",[45,49,50],{},"What happened",[45,52,53],{},"Source",[55,56,57,75,92],"tbody",{},[42,58,59,63,66],{},[60,61,62],"td",{},"2022",[60,64,65],{},"Google deprecates the SafetyNet Attestation API and recommends the Play Integrity API",[60,67,68],{},[69,70,74],"a",{"href":71,"rel":72},"https:\u002F\u002Fdeveloper.android.com\u002Fprivacy-and-security\u002Fsafetynet\u002Fdeprecation-timeline",[73],"nofollow","Android Developers",[42,76,77,82,87],{},[60,78,79],{},[17,80,81],{},"January 2025",[60,83,84],{},[17,85,86],{},"SafetyNet Attestation is fully turned down",[60,88,89],{},[69,90,74],{"href":71,"rel":91},[73],[42,93,94,97,100],{},[60,95,96],{},"Now",[60,98,99],{},"Firebase App Check's SafetyNet provider is gone; Firebase says to use another provider, such as Play Integrity",[60,101,102],{},[69,103,106],{"href":104,"rel":105},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fapp-check\u002Fandroid\u002Fsafetynet-provider",[73],"Firebase",[108,109,111],"h3",{"id":110},"what-failing-apps-see","What failing apps see",[14,113,114,115,119,120,123],{},"Google says that if you call the SafetyNet Attestation API now, the attest call always fails: it returns an ",[116,117,118],"code",{},"ApiException"," with status code 7, ",[116,121,122],{},"NETWORK_ERROR",". That code is misleading, because it isn't a network problem on your side and retrying won't fix it.",[14,125,126],{},"What that means for users depends on what you did with the result:",[128,129,130,137,143],"ul",{},[131,132,133,136],"li",{},[17,134,135],{},"If your app blocked users when attestation failed",", users on that version are blocked from whatever the check gated.",[131,138,139,142],{},[17,140,141],{},"If your backend required a SafetyNet result",", requests from that version are rejected.",[131,144,145,148],{},[17,146,147],{},"If the check only logged the result",", nothing visible broke, but you've had no integrity signal since the turndown.",[14,150,151],{},"Google's advice for the first two cases: once you've released a version that uses Play Integrity, tell users to update.",[31,153,155],{"id":154},"moving-from-safetynet-to-play-integrity","Moving from SafetyNet to Play Integrity",[14,157,158],{},"Google says the two APIs are conceptually similar, so if you already had an anti-abuse strategy built on SafetyNet, the migration is straightforward.",[160,161,162,168,180,186,192,198],"ol",{},[131,163,164,167],{},[17,165,166],{},"Set up Play Integrity in Play Console."," Open your app, go to the App integrity page, and set up the Play Integrity API there, including linking a Google Cloud project.",[131,169,170,173,174,179],{},[17,171,172],{},"Choose the request type."," Play Integrity has standard requests and classic requests. Read Google's ",[69,175,178],{"href":176,"rel":177},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Fintegrity\u002Fstandard",[73],"standard request guide"," and the classic one, and pick the one that fits how often you check.",[131,181,182,185],{},[17,183,184],{},"Request a token in the app and verify it on your server."," As with SafetyNet, the app gets an integrity token and your backend decides what to do with the verdict. Don't make the decision on the device.",[131,187,188,191],{},[17,189,190],{},"Watch the quota."," By default, an app can make up to 10,000 Play Integrity requests a day across all installs. If you check on every app open with many users, ask Google for a higher daily maximum before launch.",[131,193,194,197],{},[17,195,196],{},"Collect verdicts before you block anyone."," Google suggests gathering telemetry on your current users before you change app behavior based on verdicts, and treating Play Integrity as one part of your anti-abuse strategy, not the only one.",[131,199,200,203],{},[17,201,202],{},"Remove the SafetyNet code"," and dependency once the new path works, so nobody mistakes an old error for a new problem.",[31,205,207],{"id":206},"switching-firebase-app-check-to-play-integrity","Switching Firebase App Check to Play Integrity",[14,209,210,211,216],{},"If you used SafetyNet through Firebase App Check, the steps come from Firebase's ",[69,212,215],{"href":213,"rel":214},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fapp-check\u002Fandroid\u002Fplay-integrity-provider",[73],"Play Integrity setup guide",":",[160,218,219,229,235,241,259,271],{},[131,220,221,224,225,228],{},[17,222,223],{},"Link the projects."," In Google Play Console, open your app, go to App integrity, find the Play Integrity API section and click ",[17,226,227],{},"Link Cloud project",". Pick the same Firebase project your app is registered in. Firebase says you must be an Owner of that project yourself: being in a group that has the Owner role isn't enough.",[131,230,231,234],{},[17,232,233],{},"Register the app in Firebase."," In the Firebase console, go to Security > App Check, and register your Android app with the Play Integrity provider. You'll need the SHA-256 fingerprint of your app's signing certificate. If you use Play App Signing, use the SHA-256 of the app signing key certificate shown in Play Console, not your upload key's.",[131,236,237,240],{},[17,238,239],{},"Register every app."," Once you turn on enforcement for a Firebase product, only registered apps can reach it, so register all of your project's apps before you enforce.",[131,242,243,246,247,250,251,254,255,258],{},[17,244,245],{},"Change the provider in code."," On Android, install ",[116,248,249],{},"PlayIntegrityAppCheckProviderFactory"," before you use any other Firebase SDK. In a Flutter app, set the Android provider to Play Integrity where you activate ",[116,252,253],{},"firebase_app_check",", right after ",[116,256,257],{},"Firebase.initializeApp()",". The parameter name has changed between plugin versions, so check the docs for the version you use.",[131,260,261,264,265,270],{},[17,262,263],{},"Use the debug provider for development."," Emulators and debug builds you install yourself often can't pass Play Integrity checks. App Check's ",[69,266,269],{"href":267,"rel":268},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fapp-check\u002Fandroid\u002Fdebug-provider",[73],"debug provider"," lets those builds through. Keep its debug tokens out of release builds.",[131,272,273,276],{},[17,274,275],{},"Monitor, then enforce."," Check the App Check metrics in the Firebase console to see how many requests come from verified and unverified apps before you turn on enforcement for Firestore, Storage, Functions or other products.",[31,278,280],{"id":279},"need-a-hand","Need a hand?",[14,282,283,284,288,289,293,294,298],{},"If App Check enforcement starts rejecting your real users, or the Play Integrity setup won't verify, the ",[69,285,287],{"href":286},"\u002Ffixed-price\u002Fflutter-firebase-bug-fix\u002F","App Fix Ticket"," covers it in two fixed-price steps. A $450 Diagnosis gives you the root cause with evidence and a fixed repair quote within 3 business days, credited toward the fix. If you go ahead, the $1,200 Fix takes 5 business days. For a wider check of how your Firebase app is locked down, see the ",[69,290,292],{"href":291},"\u002Fblog\u002Ffirebase-security-guide-2026\u002F","Firebase security guide",", or ",[69,295,297],{"href":296},"\u002Fcontact\u002F","send me a short description",".",{"title":300,"searchDepth":301,"depth":301,"links":302},"",2,[303,307,308,309],{"id":33,"depth":301,"text":34,"children":304},[305],{"id":110,"depth":306,"text":111},3,{"id":154,"depth":301,"text":155},{"id":206,"depth":301,"text":207},{"id":279,"depth":301,"text":280},"AI & Technology","2026-10-19T01:00:00.000Z","Google fully turned down the SafetyNet Attestation API in January 2025, and calls now fail with error code 7. How to move to the Play Integrity API, and how to switch Firebase App Check to its Play Integrity provider, from Google's docs checked October 18, 2026.",null,"md",[316,319,322,325,328,331],{"question":317,"answer":318},"Is SafetyNet still working?","No. Google says the SafetyNet Attestation API was deprecated in 2022 and fully turned down in January 2025. Any call to it now fails.",{"question":320,"answer":321},"What does SafetyNet error code 7 mean in 2026?","It means the SafetyNet Attestation API has been turned down. Google says the attest call now always fails with an ApiException and status code 7 (NETWORK_ERROR). It isn't a network problem on your side, and retrying won't help. Move to the Play Integrity API.",{"question":323,"answer":324},"What replaced SafetyNet?","The Play Integrity API. Google says it brings several integrity checks, including what SafetyNet Attestation offered, under one API. It offers standard and classic requests, and Google's docs explain when to use each.",{"question":326,"answer":327},"Does Firebase App Check still support SafetyNet?","No. Firebase says SafetyNet has been shut down and projects must use an alternative provider, such as Play Integrity. Register your Android app with the Play Integrity provider in the App Check section of the Firebase console, then switch the provider in your app's code.",{"question":329,"answer":330},"Does Play Integrity work for apps outside Google Play?","Firebase says the App Check Play Integrity provider supports Android apps published on Google Play, outside Google Play, or both. Check the Play Integrity documentation for which verdicts you get in each case.",{"question":332,"answer":333},"How many Play Integrity requests can my app make?","By default, up to 10,000 requests per day across all installs. Google lets you request a higher daily maximum.","Boynton Beach, FL",{"readingTimeStats":336},{"text":337,"minutes":338,"time":339,"words":340},"5 min read",4.45,267000,890,true,"\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026","safetynet shut down","7 min",[346,347,348],"firebase-deprecations-shutdown-dates-2026","firebase-security-guide-2026","flutter-security-best-practices-2026",{"title":5,"description":312},[351,353,356,358,360,362],{"title":352,"url":71},"Android Developers, SafetyNet Attestation API deprecation timeline",{"title":354,"url":355},"Android Developers, Play Integrity API overview","https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Fintegrity\u002Foverview",{"title":357,"url":176},"Android Developers, make a standard Play Integrity request",{"title":359,"url":104},"Firebase, App Check SafetyNet turndown",{"title":361,"url":213},"Firebase, get started using App Check with Play Integrity on Android",{"title":363,"url":267},"Firebase, use App Check with the debug provider on Android","blog\u002Fsafetynet-shut-down-play-integrity-app-check-2026","safetynet shut down, safetynet attestation deprecated, safetynet alternative, play integrity api migration, safetynet error code 7, firebase app check safetynet, app check play integrity provider, safetynet to play integrity, android app attestation 2026, flutter app check android",[367,368,369,370,371],"Google deprecated the SafetyNet Attestation API in 2022 and fully turned it down in January 2025. Its replacement is the Play Integrity API.","An app that still calls SafetyNet attestation gets an error every time: Google says the call fails with an ApiException and status code 7 (NETWORK_ERROR).","Firebase App Check's SafetyNet provider is gone too. Firebase says projects must use another provider, such as Play Integrity.","By default, an app can make up to 10,000 Play Integrity requests a day across all installs, and you can ask Google for more.","The App Check setup steps to get right are the Cloud project link in Play Console, the correct SHA-256 fingerprint, and registering every app before you enforce.","https:\u002F\u002Fapp369.com\u002Fblog\u002Fsafetynet-shut-down-play-integrity-app-check-2026\u002F","4cEp9smwwcauYkNNVNi6OsnJ7d0IJDKMltWAqGH4A-U",[375,821,2221],{"id":376,"title":377,"author":378,"body":380,"category":310,"createdAt":759,"description":760,"disclosure":313,"extension":314,"faq":761,"howTo":313,"location":334,"meta":780,"navigation":341,"ogImage":313,"path":786,"primaryKeyword":787,"readingTime":788,"related":789,"seo":793,"sources":794,"stem":811,"tags":812,"takeaways":813,"updatedAt":759,"url":819,"__hash__":820},"blog\u002Fblog\u002Ffirebase-deprecations-shutdown-dates-2026.md","Firebase Deprecations: Every Shutdown Date, 2024 to 2027",{"name":7,"social":379},{"twitter":9},{"type":11,"value":381,"toc":747},[382,388,391,396,400,600,604,608,611,614,618,621,624,627,659,663,666,670,679,683,686,690,736,738],[14,383,384,387],{},[17,385,386],{},"Firebase isn't shutting down, but seven of its products and APIs are being retired between 2024 and 2027, and five of the deadlines fall in 2027."," If your app uses Extensions, Firebase Studio, Firebase ML model hosting, Test Lab, device groups or the old FCM InstanceId APIs, you have a deadline. The table below lists each one with the date, what stops working and where Firebase says to move.",[14,389,390],{},"Every date here comes from Firebase's own documentation, linked in each row and in the sources. I checked each page on October 18, 2026. Google can change a deadline, so open the linked page before you plan around one.",[14,392,393,395],{},[17,394,28],{}," App369 is my studio. I build Flutter and Firebase apps, and I run my own product, Workspace369, on Firebase. The last section mentions what I sell. Google doesn't pay me for anything on this page.",[31,397,399],{"id":398},"every-firebase-shutdown-in-date-order","Every Firebase shutdown, in date order",[36,401,402,420],{},[39,403,404],{},[42,405,406,409,412,415,418],{},[45,407,408],{},"Deadline",[45,410,411],{},"What is retired",[45,413,414],{},"What stops working",[45,416,417],{},"Move to",[45,419,53],{},[55,421,422,455,480,502,524,546,578],{},[42,423,424,431,434,441,448],{},[60,425,426,427,430],{},"From ",[17,428,429],{},"July 22, 2024"," (done)",[60,432,433],{},"Legacy FCM HTTP and XMPP send APIs",[60,435,436,437,440],{},"Sending with a server key to ",[116,438,439],{},"fcm\u002Fsend",". Deprecated June 20, 2023",[60,442,443,444],{},"FCM HTTP v1 API or the Admin SDK. ",[69,445,447],{"href":446},"\u002Fblog\u002Ffcm-legacy-api-shutdown-http-v1-migration-2026\u002F","My guide",[60,449,450],{},[69,451,454],{"href":452,"rel":453},"https:\u002F\u002Fweb.archive.org\u002Fweb\u002F20260108211426\u002Fhttps:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fcloud-messaging\u002Fmigrate-v1",[73],"Firebase (archived)",[42,456,457,462,465,468,474],{},[60,458,459,430],{},[17,460,461],{},"August 25, 2025",[60,463,464],{},"Firebase Dynamic Links",[60,466,467],{},"Dynamic Links, and email link sign-in that relied on them",[60,469,470,471],{},"Your own deep links. For email link sign-in, Firebase Authentication Android SDK 23.2.0 or later (BoM 33.9.0 or later). ",[69,472,447],{"href":473},"\u002Fblog\u002Ffirebase-dynamic-links-shutdown-deep-linking-2026\u002F",[60,475,476],{},[69,477,106],{"href":478,"rel":479},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fauth\u002Fandroid\u002Femail-link-migration",[73],[42,481,482,487,490,493,496],{},[60,483,484],{},[17,485,486],{},"March 22, 2027",[60,488,489],{},"Firebase Studio",[60,491,492],{},"Existing workspaces. New workspaces and signups already stopped on June 22, 2026",[60,494,495],{},"Google AI Studio, Google Antigravity, or an export of your source to another platform",[60,497,498],{},[69,499,106],{"href":500,"rel":501},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fstudio\u002Fmigrating-project",[73],[42,503,504,509,512,515,518],{},[60,505,506],{},[17,507,508],{},"March 31, 2027",[60,510,511],{},"Firebase Extensions",[60,513,514],{},"Updating, reconfiguring or uninstalling extensions, or downloading their configuration, through the console or CLI. Installed extensions keep running",[60,516,517],{},"Function kits on npm where the publisher offers one, or your own Cloud Functions (2nd gen)",[60,519,520],{},[69,521,106],{"href":522,"rel":523},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fextensions\u002Ffaq-and-troubleshooting",[73],[42,525,526,531,534,537,540],{},[60,527,528],{},[17,529,530],{},"June 15, 2027",[60,532,533],{},"Firebase ML custom model hosting",[60,535,536],{},"Hosting and serving custom models through Firebase ML",[60,538,539],{},"Cloud Storage for Firebase, per Firebase's guide",[60,541,542],{},[69,543,106],{"href":544,"rel":545},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fml\u002Fmigrate-to-cloud-storage",[73],[42,547,548,553,556,563,572],{},[60,549,550],{},[17,551,552],{},"September 29, 2027",[60,554,555],{},"FCM InstanceId server APIs and device groups",[60,557,558,559,562],{},"Requests to ",[116,560,561],{},"iid.googleapis.com"," and device group management start failing. New users are blocked after January 1, 2027",[60,564,565,566,569,570],{},"The FCM topic subscription API, HTTP v1 with ",[116,567,568],{},"validate_only",", and sends to individual tokens. ",[69,571,447],{"href":446},[60,573,574],{},[69,575,106],{"href":576,"rel":577},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fcloud-messaging\u002Ftroubleshooting#fcm-26-deprecation",[73],[42,579,580,585,588,591,594],{},[60,581,582],{},[17,583,584],{},"September 30, 2027",[60,586,587],{},"Firebase Test Lab",[60,589,590],{},"All of Test Lab",[60,592,593],{},"The Developer Device Platform on Google Cloud",[60,595,596],{},[69,597,106],{"href":598,"rel":599},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Ftest-lab",[73],[31,601,603],{"id":602},"what-each-2027-deadline-means-in-practice","What each 2027 deadline means in practice",[108,605,607],{"id":606},"firebase-studio-march-22-2027","Firebase Studio: March 22, 2027",[14,609,610],{},"Firebase Studio launched as a preview to explore AI-driven, full-stack app development. Firebase's migration page points to Google AI Studio, for rapid browser-based prototyping, and Google Antigravity, for code-first agentic work. You can keep working in an existing workspace until March 22, 2027. Any project you want to keep must be migrated before then, and Firebase's page has steps for both destinations and for exporting the source to anywhere else.",[14,612,613],{},"Your Firebase backend isn't affected. Firebase says Firestore, Authentication, App Hosting and your data keep working outside Firebase Studio. One thing to test after a move: Firebase's own FAQ has an entry about Google Sign-In not working after migrating to Google AI Studio, so check sign-in first.",[108,615,617],{"id":616},"firebase-extensions-march-31-2027","Firebase Extensions: March 31, 2027",[14,619,620],{},"This one is easy to miss, because nothing breaks on the day. Firebase says installed extensions run on standard Google Cloud services (Cloud Functions, Eventarc, Cloud Run, Cloud Tasks) and keep executing. What you lose after March 31, 2027 is control: you can't change parameters or environment variables, apply fixes or security patches, uninstall through the console or CLI, or download the configuration to set up a replacement.",[14,622,623],{},"There's a second risk. Firebase notes that most extensions are built on the legacy Cloud Functions v1 SDK and older Node.js runtimes, and that functions may stop running or be disabled when Google Cloud retires those runtimes. So an extension you can't update may stop working once those runtimes are retired, and you won't be able to patch it.",[14,625,626],{},"Before March 31, 2027:",[160,628,629,635,641,647],{},[131,630,631,634],{},[17,632,633],{},"List what's installed."," In the Firebase console, open Extensions for each project, including staging and old projects.",[131,636,637,640],{},[17,638,639],{},"Uninstall what you no longer use."," After the deadline the Uninstall button goes away, and you'd have to delete the underlying resources by hand.",[131,642,643,646],{},[17,644,645],{},"Export the configuration of the rest."," You need the parameters to set up a replacement.",[131,648,649,652,653,658],{},[17,650,651],{},"Replace each one."," Use the publisher's function kit on npm if there is one. If not, the extensions are open source, so a developer can rebuild one as Cloud Functions (2nd gen). Firebase's ",[69,654,657],{"href":655,"rel":656},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fextensions\u002Fusers\u002Fmigrate",[73],"guide for users"," covers both.",[108,660,662],{"id":661},"firebase-ml-model-hosting-june-15-2027","Firebase ML model hosting: June 15, 2027",[14,664,665],{},"This only matters if you host custom TensorFlow Lite models through Firebase ML. On-device ML Kit features are a separate product. Firebase's suggested route is to download your models and host them in Cloud Storage for Firebase, and its guide covers Android, Apple platforms and Flutter, plus an optional move to the LiteRT CompiledModel API.",[108,667,669],{"id":668},"fcm-instanceid-apis-and-device-groups-september-29-2027","FCM InstanceId APIs and device groups: September 29, 2027",[14,671,672,673,675,676,298],{},"If your server checks tokens with ",[116,674,561],{},", manages topic subscriptions through the old InstanceId endpoints, imports APNs tokens in bulk, or uses device groups, those calls start failing after September 29, 2027. If you manage topics through an older Firebase Admin SDK, you're affected too. I cover each case and the replacement in the ",[69,677,678],{"href":446},"FCM guide",[108,680,682],{"id":681},"test-lab-september-30-2027","Test Lab: September 30, 2027",[14,684,685],{},"Test Lab runs your Android and iOS tests on devices in Google's data centers. Firebase says it will be fully shut down on September 30, 2027, and points to the Developer Device Platform on Google Cloud, with a migration guide and FAQ linked from the Test Lab page. If Test Lab runs in your CI pipeline, budget time to move it and to compare device coverage and pricing before the deadline.",[31,687,689],{"id":688},"a-one-hour-check-for-your-project","A one-hour check for your project",[160,691,692,718,724,730],{},[131,693,694,697,698,700,701,700,704,700,707,710,711,714,715,298],{},[17,695,696],{},"Search your code"," for ",[116,699,561],{},", ",[116,702,703],{},"fcm.googleapis.com\u002Ffcm\u002F",[116,705,706],{},"page.link",[116,708,709],{},"firebase-ml-modeldownloader"," (or ",[116,712,713],{},"firebase_ml_model_downloader"," in Flutter), and ",[116,716,717],{},"gcloud firebase test",[131,719,720,723],{},[17,721,722],{},"Open the Firebase console"," for every project you own and look at Extensions, Machine Learning, Test Lab and Studio.",[131,725,726,729],{},[17,727,728],{},"Write down each deadline"," that applies, with the owner of the fix and a target date at least a month early.",[131,731,732,735],{},[17,733,734],{},"Start with the earliest deadlines."," Of the 2027 ones, Studio and Extensions come first, in March.",[31,737,280],{"id":279},[14,739,740,741,743,744,746],{},"If one of these migrations breaks something, such as pushes that stop arriving, a sign-in flow that fails after a move, or a Cloud Function that won't deploy, the ",[69,742,287],{"href":286}," covers it in two fixed-price steps. A $450 Diagnosis gives you the root cause with evidence and a fixed repair quote within 3 business days, credited toward the fix. If you go ahead, the $1,200 Fix takes 5 business days. If you want help planning several migrations at once, ",[69,745,297],{"href":296}," of your project.",{"title":300,"searchDepth":301,"depth":301,"links":748},[749,750,757,758],{"id":398,"depth":301,"text":399},{"id":602,"depth":301,"text":603,"children":751},[752,753,754,755,756],{"id":606,"depth":306,"text":607},{"id":616,"depth":306,"text":617},{"id":661,"depth":306,"text":662},{"id":668,"depth":306,"text":669},{"id":681,"depth":306,"text":682},{"id":688,"depth":301,"text":689},{"id":279,"depth":301,"text":280},"2026-10-18T23:00:00.000Z","Every Firebase shutdown from 2024 to 2027 in one dated table: FCM legacy APIs, Dynamic Links, Firebase Studio, Extensions, Firebase ML model hosting, the FCM InstanceId APIs and Test Lab. What stops working, the deadline, and what to move to, from Firebase's own docs checked October 18, 2026.",[762,765,768,771,774,777],{"question":763,"answer":764},"Is Firebase shutting down?","No. Firebase is retiring some products and APIs, but core services such as Firestore, Authentication, Cloud Functions and Hosting are not on any shutdown list I found. Firebase says the Firebase Studio sunset doesn't affect Firestore, Authentication or App Hosting.",{"question":766,"answer":767},"What Firebase products are being deprecated in 2027?","Firebase Studio sunsets on March 22, 2027, Firebase Extensions shuts down on March 31, 2027, Firebase ML custom model hosting on June 15, 2027, the FCM InstanceId server APIs and device groups on September 29, 2027, and Test Lab on September 30, 2027.",{"question":769,"answer":770},"Will my Firebase Extensions stop working on March 31, 2027?","Not immediately. Firebase says already-deployed extensions keep running on Google Cloud infrastructure, but after March 31, 2027 you can't update, reconfigure or uninstall them through the Firebase console or CLI, or download their configuration. Firebase also warns that most extensions use the legacy Cloud Functions v1 SDK, which may stop running when older runtimes are decommissioned.",{"question":772,"answer":773},"What replaces Firebase Test Lab?","Firebase points Test Lab users to the Developer Device Platform on Google Cloud, with a migration guide and FAQ. Test Lab will be fully shut down on September 30, 2027.",{"question":775,"answer":776},"What happens to apps I built in Firebase Studio?","New workspaces and signups were disabled on June 22, 2026. You can keep working in existing workspaces until March 22, 2027, but you must migrate any project you want to keep before then. Firebase offers migration paths to Google AI Studio, Google Antigravity, or exporting the source to another platform.",{"question":778,"answer":779},"Where can I host custom ML models after Firebase ML?","Firebase suggests Cloud Storage for Firebase and has a guide for downloading your TensorFlow Lite models and uploading them there, with steps for Android, Apple platforms and Flutter. Firebase ML model hosting shuts down on June 15, 2027.",{"readingTimeStats":781},{"text":782,"minutes":783,"time":784,"words":785},"6 min read",5.255,315300,1051,"\u002Fblog\u002Ffirebase-deprecations-shutdown-dates-2026","firebase deprecations","8 min",[790,791,792],"fcm-legacy-api-shutdown-http-v1-migration-2026","firebase-dynamic-links-shutdown-deep-linking-2026","how-to-cut-your-firebase-bill-2026",{"title":377,"description":760},[795,797,799,801,803,805,807,809],{"title":796,"url":452},"Firebase, migrate from legacy FCM APIs to HTTP v1 (Wayback Machine copy, January 8, 2026)",{"title":798,"url":478},"Firebase, migrating email link sign-in off Dynamic Links (Android)",{"title":800,"url":500},"Firebase, Firebase Studio sunset and project migration",{"title":802,"url":522},"Firebase, Firebase Extensions deprecation FAQ",{"title":804,"url":655},"Firebase, migrate an extension to Cloud Functions as a user",{"title":806,"url":544},"Firebase, migrate custom models from Firebase ML to Cloud Storage",{"title":808,"url":576},"Firebase, FCM InstanceId API and device group deprecation (FAQ, announced September 2026)",{"title":810,"url":598},"Firebase, Test Lab (deprecation notice)","blog\u002Ffirebase-deprecations-shutdown-dates-2026","firebase deprecations, firebase shutdown dates, firebase extensions deprecated, firebase test lab shut down, firebase studio sunset, firebase ml model hosting shut down, firebase dynamic links shutdown, fcm legacy api shutdown, firebase deprecation 2027, firebase migration checklist",[814,815,816,817,818],"Five Firebase deadlines fall between March and September 2027: Firebase Studio (March 22), Extensions (March 31), Firebase ML model hosting (June 15), the FCM InstanceId APIs and device groups (September 29) and Test Lab (September 30).","Two shutdowns have already happened: sending through the legacy FCM APIs, from July 22, 2024, and Firebase Dynamic Links, on August 25, 2025.","Core services such as Firestore, Authentication, Cloud Functions and Hosting are not on the list, and Firebase says the Studio sunset doesn't affect Firestore, Authentication or App Hosting.","Installed Firebase Extensions keep running after March 31, 2027, but you can no longer update, reconfigure or uninstall them through Firebase, so migrate or uninstall them before then.","Every date here comes from Firebase's own documentation, checked on October 18, 2026. Deadlines can move, so check the linked page before you plan around one.","https:\u002F\u002Fapp369.com\u002Fblog\u002Ffirebase-deprecations-shutdown-dates-2026\u002F","iqe7_rdKT5YLVNZFpSyM8FRIxPr_4--3wAp_MkpS6EE",{"id":822,"title":823,"author":824,"body":826,"category":310,"createdAt":2201,"description":2202,"disclosure":313,"extension":314,"faq":313,"howTo":313,"location":334,"meta":2203,"navigation":341,"ogImage":313,"path":2209,"primaryKeyword":313,"readingTime":2210,"related":2211,"seo":2215,"sources":313,"stem":2216,"tags":2217,"takeaways":313,"updatedAt":2218,"url":2219,"__hash__":2220},"blog\u002Fblog\u002Ffirebase-security-guide-2026.md","Firebase Security Rules: The Complete Guide for App Developers (2026)",{"name":7,"social":825},{"twitter":9},{"type":11,"value":827,"toc":2185},[828,840,848,852,855,864,867,887,890,894,911,914,970,973,979,1017,1020,1033,1037,1046,1049,1186,1189,1251,1254,1258,1267,1270,1323,1326,1378,1387,1390,1414,1418,1421,1424,1527,1530,1607,1610,1614,1623,1631,1636,1660,1669,1781,1784,1827,1831,1834,1837,1843,1932,1938,1988,1994,2059,2062,2066,2072,2116,2127,2131,2135,2143,2147,2150,2154,2169,2173,2181],[14,829,830,833,834,839],{},[17,831,832],{},"Firebase Security Rules are the primary access control mechanism between your users and your data."," According to ",[69,835,838],{"href":836,"rel":837},"https:\u002F\u002Fwww.audityour.app\u002Fguides\u002Ffirebase-security-rules-guide",[73],"AuditYourApp's Firebase security analysis",", roughly 30% of Firebase applications in production have overly permissive rules on at least one collection, meaning nearly one in three apps allows unauthorized reads, writes, or both. Firebase does not enforce any access restrictions by default beyond the initial test\u002Fproduction mode selection, so every permission must be explicitly defined by the developer.",[14,841,842,843,847],{},"This guide covers the specific rule patterns, validation techniques, and deployment workflows that ",[69,844,846],{"href":845},"\u002Fservice\u002Fmobile-apps\u002F","App369"," uses to secure Firebase backends across production applications.",[31,849,851],{"id":850},"why-firebase-security-rules-matter","Why Firebase Security Rules Matter",[14,853,854],{},"Firebase Security Rules operate as a server-side access control layer that evaluates every read and write request before it reaches your database. Without properly configured rules, any user with your Firebase project's configuration (which is embedded in every client app and publicly visible) can read, modify, or delete data directly through the Firebase SDK or REST API.",[14,856,857,858,863],{},"According to ",[69,859,862],{"href":860,"rel":861},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Frules\u002F",[73],"Firebase's official documentation",", Security Rules provide fine-grained authorization that cannot be bypassed from client code. Unlike client-side validation, which can be disabled by modifying the app, Security Rules execute on Firebase's servers and cannot be circumvented.",[14,865,866],{},"Rules apply to three Firebase services independently:",[128,868,869,875,881],{},[131,870,871,874],{},[17,872,873],{},"Cloud Firestore",": document-level read\u002Fwrite control with path-based matching",[131,876,877,880],{},[17,878,879],{},"Realtime Database",": node-level access with cascading permissions",[131,882,883,886],{},[17,884,885],{},"Cloud Storage",": file-level access based on path, metadata, and authentication state",[14,888,889],{},"Each service requires its own rules file. Securing Firestore does not secure Storage, and vice versa.",[31,891,893],{"id":892},"the-test-mode-trap-firebases-biggest-vulnerability","The Test Mode Trap: Firebase's Biggest Vulnerability",[14,895,896,897,900,901,904,905,910],{},"Firebase's setup wizard offers two starting configurations: ",[17,898,899],{},"test mode"," and ",[17,902,903],{},"locked mode",". Test mode grants public read and write access to the entire database for 30 days. According to ",[69,906,909],{"href":907,"rel":908},"https:\u002F\u002Fvibeappscanner.com\u002Fis-firebase-safe",[73],"VibeAppScanner's Firebase safety analysis",", deploying with test mode rules is the single most common Firebase security failure in production.",[14,912,913],{},"Test mode rules look like this:",[915,916,920],"pre",{"className":917,"code":918,"language":919,"meta":300,"style":300},"language-javascript shiki shiki-themes github-light github-dark","rules_version = '2';\nservice cloud.firestore {\n  match \u002Fdatabases\u002F{database}\u002Fdocuments {\n    match \u002F{document=**} {\n      allow read, write: if request.time \u003C timestamp.date(2026, 4, 7);\n    }\n  }\n}\n","javascript",[116,921,922,930,935,940,946,952,958,964],{"__ignoreMap":300},[923,924,927],"span",{"class":925,"line":926},"line",1,[923,928,929],{},"rules_version = '2';\n",[923,931,932],{"class":925,"line":301},[923,933,934],{},"service cloud.firestore {\n",[923,936,937],{"class":925,"line":306},[923,938,939],{},"  match \u002Fdatabases\u002F{database}\u002Fdocuments {\n",[923,941,943],{"class":925,"line":942},4,[923,944,945],{},"    match \u002F{document=**} {\n",[923,947,949],{"class":925,"line":948},5,[923,950,951],{},"      allow read, write: if request.time \u003C timestamp.date(2026, 4, 7);\n",[923,953,955],{"class":925,"line":954},6,[923,956,957],{},"    }\n",[923,959,961],{"class":925,"line":960},7,[923,962,963],{},"  }\n",[923,965,967],{"class":925,"line":966},8,[923,968,969],{},"}\n",[14,971,972],{},"The timestamp creates an illusion of safety: after the date passes, all access is blocked, breaking the app entirely. Before the date, anyone on the internet can read and overwrite every document. Both outcomes are unacceptable in production.",[14,974,975,976,978],{},"The correct starting point is ",[17,977,903],{},", where all access is denied by default:",[915,980,982],{"className":917,"code":981,"language":919,"meta":300,"style":300},"rules_version = '2';\nservice cloud.firestore {\n  match \u002Fdatabases\u002F{database}\u002Fdocuments {\n    match \u002F{document=**} {\n      allow read, write: if false;\n    }\n  }\n}\n",[116,983,984,988,992,996,1000,1005,1009,1013],{"__ignoreMap":300},[923,985,986],{"class":925,"line":926},[923,987,929],{},[923,989,990],{"class":925,"line":301},[923,991,934],{},[923,993,994],{"class":925,"line":306},[923,995,939],{},[923,997,998],{"class":925,"line":942},[923,999,945],{},[923,1001,1002],{"class":925,"line":948},[923,1003,1004],{},"      allow read, write: if false;\n",[923,1006,1007],{"class":925,"line":954},[923,1008,957],{},[923,1010,1011],{"class":925,"line":960},[923,1012,963],{},[923,1014,1015],{"class":925,"line":966},[923,1016,969],{},[14,1018,1019],{},"From this baseline, add permissions incrementally for each collection, specifying exactly who can perform which operations.",[14,1021,1022,1023,1027,1028,1032],{},"AI app builders often leave test-mode rules in place instead of a locked baseline like this one. If your Lovable, Bolt or Cursor app has open data, sign-in loops or failing deploys, the ",[69,1024,1026],{"href":1025},"\u002Ffixed-price\u002Fai-app-rescue\u002F","AI-built app rescue"," starts with a $450 Launch Check. On Supabase rather than Firebase, the free ",[69,1029,1031],{"href":1030},"\u002Ftools\u002Fsupabase-oct-30-checker\u002F","Supabase October 30 grants checker"," lists the tables that will need explicit grants after that date.",[31,1034,1036],{"id":1035},"writing-firestore-security-rules-that-actually-work","Writing Firestore Security Rules That Actually Work",[14,1038,1039,1040,1045],{},"Firestore rules use path matching with wildcard variables to control access at the collection and document level. According to ",[69,1041,1044],{"href":1042,"rel":1043},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Ffirestore\u002Fsecurity\u002Fget-started",[73],"Firebase's Firestore security documentation",", if several rules match a request, access is allowed when any one of them allows it, so a broad rule can't be narrowed by a more specific one. Keep each allow condition as tight as it can be.",[14,1047,1048],{},"Here is a production-ready pattern for a user-owned data collection:",[915,1050,1052],{"className":917,"code":1051,"language":919,"meta":300,"style":300},"rules_version = '2';\nservice cloud.firestore {\n  match \u002Fdatabases\u002F{database}\u002Fdocuments {\n\n    \u002F\u002F Users can only read and write their own profile\n    match \u002Fusers\u002F{userId} {\n      allow read, write: if request.auth != null && request.auth.uid == userId;\n    }\n\n    \u002F\u002F Posts are publicly readable, but only the author can write\n    match \u002Fposts\u002F{postId} {\n      allow read: if true;\n      allow create: if request.auth != null\n                    && request.resource.data.authorId == request.auth.uid;\n      allow update, delete: if request.auth != null\n                            && resource.data.authorId == request.auth.uid;\n    }\n\n    \u002F\u002F Admin-only collection\n    match \u002FadminConfig\u002F{docId} {\n      allow read, write: if request.auth != null\n                         && request.auth.token.admin == true;\n    }\n  }\n}\n",[116,1053,1054,1058,1062,1066,1071,1076,1081,1086,1090,1095,1101,1107,1113,1119,1125,1131,1137,1142,1147,1153,1159,1165,1171,1176,1181],{"__ignoreMap":300},[923,1055,1056],{"class":925,"line":926},[923,1057,929],{},[923,1059,1060],{"class":925,"line":301},[923,1061,934],{},[923,1063,1064],{"class":925,"line":306},[923,1065,939],{},[923,1067,1068],{"class":925,"line":942},[923,1069,1070],{"emptyLinePlaceholder":341},"\n",[923,1072,1073],{"class":925,"line":948},[923,1074,1075],{},"    \u002F\u002F Users can only read and write their own profile\n",[923,1077,1078],{"class":925,"line":954},[923,1079,1080],{},"    match \u002Fusers\u002F{userId} {\n",[923,1082,1083],{"class":925,"line":960},[923,1084,1085],{},"      allow read, write: if request.auth != null && request.auth.uid == userId;\n",[923,1087,1088],{"class":925,"line":966},[923,1089,957],{},[923,1091,1093],{"class":925,"line":1092},9,[923,1094,1070],{"emptyLinePlaceholder":341},[923,1096,1098],{"class":925,"line":1097},10,[923,1099,1100],{},"    \u002F\u002F Posts are publicly readable, but only the author can write\n",[923,1102,1104],{"class":925,"line":1103},11,[923,1105,1106],{},"    match \u002Fposts\u002F{postId} {\n",[923,1108,1110],{"class":925,"line":1109},12,[923,1111,1112],{},"      allow read: if true;\n",[923,1114,1116],{"class":925,"line":1115},13,[923,1117,1118],{},"      allow create: if request.auth != null\n",[923,1120,1122],{"class":925,"line":1121},14,[923,1123,1124],{},"                    && request.resource.data.authorId == request.auth.uid;\n",[923,1126,1128],{"class":925,"line":1127},15,[923,1129,1130],{},"      allow update, delete: if request.auth != null\n",[923,1132,1134],{"class":925,"line":1133},16,[923,1135,1136],{},"                            && resource.data.authorId == request.auth.uid;\n",[923,1138,1140],{"class":925,"line":1139},17,[923,1141,957],{},[923,1143,1145],{"class":925,"line":1144},18,[923,1146,1070],{"emptyLinePlaceholder":341},[923,1148,1150],{"class":925,"line":1149},19,[923,1151,1152],{},"    \u002F\u002F Admin-only collection\n",[923,1154,1156],{"class":925,"line":1155},20,[923,1157,1158],{},"    match \u002FadminConfig\u002F{docId} {\n",[923,1160,1162],{"class":925,"line":1161},21,[923,1163,1164],{},"      allow read, write: if request.auth != null\n",[923,1166,1168],{"class":925,"line":1167},22,[923,1169,1170],{},"                         && request.auth.token.admin == true;\n",[923,1172,1174],{"class":925,"line":1173},23,[923,1175,957],{},[923,1177,1179],{"class":925,"line":1178},24,[923,1180,963],{},[923,1182,1184],{"class":925,"line":1183},25,[923,1185,969],{},[14,1187,1188],{},"Key principles for effective rules:",[128,1190,1191,1215,1224,1237],{},[131,1192,1193,1196,1197,900,1200,1203,1204,700,1207,1210,1211,1214],{},[17,1194,1195],{},"Separate read and write permissions."," Use ",[116,1198,1199],{},"allow read",[116,1201,1202],{},"allow write"," independently. Break write into ",[116,1205,1206],{},"create",[116,1208,1209],{},"update",", and ",[116,1212,1213],{},"delete"," when different logic applies.",[131,1216,1217,1223],{},[17,1218,1219,1220,298],{},"Always check ",[116,1221,1222],{},"request.auth != null"," This confirms the user is authenticated. Without this check, unauthenticated users can access the data.",[131,1225,1226,900,1233,1236],{},[17,1227,1228,1229,1232],{},"Use ",[116,1230,1231],{},"resource.data"," for existing document fields",[116,1234,1235],{},"request.resource.data"," for incoming data in create\u002Fupdate operations.",[131,1238,1239,1242,1243,1246,1247,1250],{},[17,1240,1241],{},"Avoid wildcards at the root level."," The pattern ",[116,1244,1245],{},"match \u002F{document=**}"," applies to every document in the database. Use it only with ",[116,1248,1249],{},"if false"," as a catch-all deny rule.",[14,1252,1253],{},"Structure your database with security in mind. Organize collections by permission boundary: keep admin-only data in separate collections rather than mixing it with user-readable documents.",[31,1255,1257],{"id":1256},"role-based-access-control-with-custom-claims","Role-Based Access Control with Custom Claims",[14,1259,1260,1261,1266],{},"Firebase Authentication custom claims provide the foundation for role-based access control (RBAC). According to ",[69,1262,1265],{"href":1263,"rel":1264},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fauth\u002Fadmin\u002Fcustom-claims",[73],"Firebase's custom claims documentation",", claims are set server-side using the Admin SDK and propagated to the client through the ID token.",[14,1268,1269],{},"Set custom claims from a Cloud Function or admin server:",[915,1271,1273],{"className":917,"code":1272,"language":919,"meta":300,"style":300},"const admin = require('firebase-admin');\n\n\u002F\u002F Assign admin role\nawait admin.auth().setCustomUserClaims(userId, { admin: true });\n\n\u002F\u002F Assign editor role with department\nawait admin.auth().setCustomUserClaims(userId, {\n  role: 'editor',\n  department: 'marketing'\n});\n",[116,1274,1275,1280,1284,1289,1294,1298,1303,1308,1313,1318],{"__ignoreMap":300},[923,1276,1277],{"class":925,"line":926},[923,1278,1279],{},"const admin = require('firebase-admin');\n",[923,1281,1282],{"class":925,"line":301},[923,1283,1070],{"emptyLinePlaceholder":341},[923,1285,1286],{"class":925,"line":306},[923,1287,1288],{},"\u002F\u002F Assign admin role\n",[923,1290,1291],{"class":925,"line":942},[923,1292,1293],{},"await admin.auth().setCustomUserClaims(userId, { admin: true });\n",[923,1295,1296],{"class":925,"line":948},[923,1297,1070],{"emptyLinePlaceholder":341},[923,1299,1300],{"class":925,"line":954},[923,1301,1302],{},"\u002F\u002F Assign editor role with department\n",[923,1304,1305],{"class":925,"line":960},[923,1306,1307],{},"await admin.auth().setCustomUserClaims(userId, {\n",[923,1309,1310],{"class":925,"line":966},[923,1311,1312],{},"  role: 'editor',\n",[923,1314,1315],{"class":925,"line":1092},[923,1316,1317],{},"  department: 'marketing'\n",[923,1319,1320],{"class":925,"line":1097},[923,1321,1322],{},"});\n",[14,1324,1325],{},"Reference claims in Security Rules:",[915,1327,1329],{"className":917,"code":1328,"language":919,"meta":300,"style":300},"match \u002Fcontent\u002F{docId} {\n  \u002F\u002F Admins have full access\n  allow read, write: if request.auth.token.admin == true;\n\n  \u002F\u002F Editors can read and update, but not delete\n  allow read, update: if request.auth.token.role == 'editor';\n\n  \u002F\u002F All authenticated users can read\n  allow read: if request.auth != null;\n}\n",[116,1330,1331,1336,1341,1346,1350,1355,1360,1364,1369,1374],{"__ignoreMap":300},[923,1332,1333],{"class":925,"line":926},[923,1334,1335],{},"match \u002Fcontent\u002F{docId} {\n",[923,1337,1338],{"class":925,"line":301},[923,1339,1340],{},"  \u002F\u002F Admins have full access\n",[923,1342,1343],{"class":925,"line":306},[923,1344,1345],{},"  allow read, write: if request.auth.token.admin == true;\n",[923,1347,1348],{"class":925,"line":942},[923,1349,1070],{"emptyLinePlaceholder":341},[923,1351,1352],{"class":925,"line":948},[923,1353,1354],{},"  \u002F\u002F Editors can read and update, but not delete\n",[923,1356,1357],{"class":925,"line":954},[923,1358,1359],{},"  allow read, update: if request.auth.token.role == 'editor';\n",[923,1361,1362],{"class":925,"line":960},[923,1363,1070],{"emptyLinePlaceholder":341},[923,1365,1366],{"class":925,"line":966},[923,1367,1368],{},"  \u002F\u002F All authenticated users can read\n",[923,1370,1371],{"class":925,"line":1092},[923,1372,1373],{},"  allow read: if request.auth != null;\n",[923,1375,1376],{"class":925,"line":1097},[923,1377,969],{},[14,1379,1380,1381,1386],{},"As noted in ",[69,1382,1385],{"href":1383,"rel":1384},"https:\u002F\u002Fmedium.com\u002F@NadunKulatunge\u002Fadvanced-use-cases-and-best-practices-of-firebase-security-rules-cb5d3339d8a7",[73],"Nadun Kulatunge's advanced Firebase rules guide on Medium",", custom claims should be limited to authorization data only: role identifiers, permission flags, and department codes. Do not store user preferences or profile data in claims, as the token has a size limit and claims are only refreshed when the token is re-issued (typically every hour).",[14,1388,1389],{},"Implementation guidelines:",[128,1391,1392,1398,1408],{},[131,1393,1394,1397],{},[17,1395,1396],{},"Limit claim size."," Custom claims are included in every authenticated request. Keep them under 1,000 bytes total.",[131,1399,1400,1403,1404,1407],{},[17,1401,1402],{},"Force token refresh after claim changes."," Call ",[116,1405,1406],{},"user.getIdToken(true)"," on the client after updating claims server-side, or the old claims persist until the token auto-refreshes.",[131,1409,1410,1413],{},[17,1411,1412],{},"Use hierarchical roles."," Define roles with clear inheritance (viewer \u003C editor \u003C admin) so rules remain readable as the permission model grows.",[31,1415,1417],{"id":1416},"data-validation-in-security-rules","Data Validation in Security Rules",[14,1419,1420],{},"Security Rules can enforce data structure requirements directly, preventing malformed data from reaching the database regardless of what the client sends. This is the first line of defense against both bugs and deliberate manipulation.",[14,1422,1423],{},"Validation patterns for common requirements:",[915,1425,1427],{"className":917,"code":1426,"language":919,"meta":300,"style":300},"match \u002Fusers\u002F{userId} {\n  allow create: if request.auth.uid == userId\n    \u002F\u002F Required fields must exist\n    && request.resource.data.keys().hasAll(['name', 'email', 'createdAt'])\n    \u002F\u002F Name must be a string between 2 and 100 characters\n    && request.resource.data.name is string\n    && request.resource.data.name.size() >= 2\n    && request.resource.data.name.size() \u003C= 100\n    \u002F\u002F Email must be a string and match the auth email\n    && request.resource.data.email is string\n    && request.resource.data.email == request.auth.token.email\n    \u002F\u002F createdAt must be a timestamp\n    && request.resource.data.createdAt is timestamp\n    \u002F\u002F No unexpected fields allowed\n    && request.resource.data.keys().hasOnly(['name', 'email', 'createdAt', 'bio', 'avatarUrl']);\n\n  allow update: if request.auth.uid == userId\n    \u002F\u002F Prevent changing the email field\n    && (!request.resource.data.diff(resource.data).affectedKeys().hasAny(['email', 'createdAt']));\n}\n",[116,1428,1429,1434,1439,1444,1449,1454,1459,1464,1469,1474,1479,1484,1489,1494,1499,1504,1508,1513,1518,1523],{"__ignoreMap":300},[923,1430,1431],{"class":925,"line":926},[923,1432,1433],{},"match \u002Fusers\u002F{userId} {\n",[923,1435,1436],{"class":925,"line":301},[923,1437,1438],{},"  allow create: if request.auth.uid == userId\n",[923,1440,1441],{"class":925,"line":306},[923,1442,1443],{},"    \u002F\u002F Required fields must exist\n",[923,1445,1446],{"class":925,"line":942},[923,1447,1448],{},"    && request.resource.data.keys().hasAll(['name', 'email', 'createdAt'])\n",[923,1450,1451],{"class":925,"line":948},[923,1452,1453],{},"    \u002F\u002F Name must be a string between 2 and 100 characters\n",[923,1455,1456],{"class":925,"line":954},[923,1457,1458],{},"    && request.resource.data.name is string\n",[923,1460,1461],{"class":925,"line":960},[923,1462,1463],{},"    && request.resource.data.name.size() >= 2\n",[923,1465,1466],{"class":925,"line":966},[923,1467,1468],{},"    && request.resource.data.name.size() \u003C= 100\n",[923,1470,1471],{"class":925,"line":1092},[923,1472,1473],{},"    \u002F\u002F Email must be a string and match the auth email\n",[923,1475,1476],{"class":925,"line":1097},[923,1477,1478],{},"    && request.resource.data.email is string\n",[923,1480,1481],{"class":925,"line":1103},[923,1482,1483],{},"    && request.resource.data.email == request.auth.token.email\n",[923,1485,1486],{"class":925,"line":1109},[923,1487,1488],{},"    \u002F\u002F createdAt must be a timestamp\n",[923,1490,1491],{"class":925,"line":1115},[923,1492,1493],{},"    && request.resource.data.createdAt is timestamp\n",[923,1495,1496],{"class":925,"line":1121},[923,1497,1498],{},"    \u002F\u002F No unexpected fields allowed\n",[923,1500,1501],{"class":925,"line":1127},[923,1502,1503],{},"    && request.resource.data.keys().hasOnly(['name', 'email', 'createdAt', 'bio', 'avatarUrl']);\n",[923,1505,1506],{"class":925,"line":1133},[923,1507,1070],{"emptyLinePlaceholder":341},[923,1509,1510],{"class":925,"line":1139},[923,1511,1512],{},"  allow update: if request.auth.uid == userId\n",[923,1514,1515],{"class":925,"line":1144},[923,1516,1517],{},"    \u002F\u002F Prevent changing the email field\n",[923,1519,1520],{"class":925,"line":1149},[923,1521,1522],{},"    && (!request.resource.data.diff(resource.data).affectedKeys().hasAny(['email', 'createdAt']));\n",[923,1524,1525],{"class":925,"line":1155},[923,1526,969],{},[14,1528,1529],{},"Validation capabilities in Firestore rules include:",[128,1531,1532,1559,1569,1583,1592,1598],{},[131,1533,1534,1537,1538,700,1541,700,1544,700,1547,700,1550,700,1553,293,1556],{},[17,1535,1536],{},"Type checking",": verify that fields are ",[116,1539,1540],{},"string",[116,1542,1543],{},"int",[116,1545,1546],{},"float",[116,1548,1549],{},"bool",[116,1551,1552],{},"timestamp",[116,1554,1555],{},"list",[116,1557,1558],{},"map",[131,1560,1561,1564,1565,1568],{},[17,1562,1563],{},"String length",": use ",[116,1566,1567],{},".size()"," to enforce minimum and maximum lengths",[131,1570,1571,1574,1575,1578,1579,1582],{},[17,1572,1573],{},"Field existence",": ",[116,1576,1577],{},"hasAll()"," for required fields, ",[116,1580,1581],{},"hasOnly()"," to prevent extra fields",[131,1584,1585,1564,1588,1591],{},[17,1586,1587],{},"Field immutability",[116,1589,1590],{},".diff().affectedKeys()"," to prevent modification of specific fields after creation",[131,1593,1594,1597],{},[17,1595,1596],{},"Numeric ranges",": enforce minimum and maximum values for numbers",[131,1599,1600,1603,1604,1606],{},[17,1601,1602],{},"List constraints",": check ",[116,1605,1567],{}," on lists to limit array lengths",[14,1608,1609],{},"Data validation in rules complements client-side validation but must never depend on it. Treat every incoming write as potentially malicious.",[31,1611,1613],{"id":1612},"testing-and-deploying-rules-safely","Testing and Deploying Rules Safely",[14,1615,1616,1617,1622],{},"Firebase provides a rules simulator in the Firebase Console and a local emulator suite for testing rules before deployment. Deploying untested rules is a common way for mistakes to reach production; ",[69,1618,1621],{"href":1619,"rel":1620},"https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Frules\u002Funit-tests",[73],"Firebase's testing documentation"," shows how to unit test rules with the emulator.",[14,1624,1625,1626,1630],{},"For a quick first pass, paste your rules into App369's free ",[69,1627,1629],{"href":1628},"\u002Ftools\u002Ffirebase-rules-checker\u002F","Firebase Security Rules checker",". It flags public paths, leftover test-mode rules and sign-in-only checks, and runs in your browser.",[14,1632,1633],{},[17,1634,1635],{},"Use the Firebase Emulator Suite for local testing:",[915,1637,1641],{"className":1638,"code":1639,"language":1640,"meta":300,"style":300},"language-bash shiki shiki-themes github-light github-dark","firebase emulators:start --only firestore\n","bash",[116,1642,1643],{"__ignoreMap":300},[923,1644,1645,1649,1653,1657],{"class":925,"line":926},[923,1646,1648],{"class":1647},"sScJk","firebase",[923,1650,1652],{"class":1651},"sZZnC"," emulators:start",[923,1654,1656],{"class":1655},"sj4cs"," --only",[923,1658,1659],{"class":1651}," firestore\n",[14,1661,1662],{},[17,1663,1664,1665,1668],{},"Write unit tests with the ",[116,1666,1667],{},"@firebase\u002Frules-unit-testing"," package:",[915,1670,1672],{"className":917,"code":1671,"language":919,"meta":300,"style":300},"const { initializeTestEnvironment, assertSucceeds, assertFails } = require('@firebase\u002Frules-unit-testing');\n\nconst testEnv = await initializeTestEnvironment({\n  projectId: 'your-project-id',\n  firestore: { rules: fs.readFileSync('firestore.rules', 'utf8') }\n});\n\n\u002F\u002F Test: authenticated user can read their own document\nconst userContext = testEnv.authenticatedContext('user123');\nawait assertSucceeds(\n  userContext.firestore().collection('users').doc('user123').get()\n);\n\n\u002F\u002F Test: user cannot read another user's document\nawait assertFails(\n  userContext.firestore().collection('users').doc('other-user').get()\n);\n\n\u002F\u002F Test: unauthenticated user cannot write\nconst unauthedContext = testEnv.unauthenticatedContext();\nawait assertFails(\n  unauthedContext.firestore().collection('users').doc('user123').set({ name: 'test' })\n);\n",[116,1673,1674,1679,1683,1688,1693,1698,1702,1706,1711,1716,1721,1726,1731,1735,1740,1745,1750,1754,1758,1763,1768,1772,1777],{"__ignoreMap":300},[923,1675,1676],{"class":925,"line":926},[923,1677,1678],{},"const { initializeTestEnvironment, assertSucceeds, assertFails } = require('@firebase\u002Frules-unit-testing');\n",[923,1680,1681],{"class":925,"line":301},[923,1682,1070],{"emptyLinePlaceholder":341},[923,1684,1685],{"class":925,"line":306},[923,1686,1687],{},"const testEnv = await initializeTestEnvironment({\n",[923,1689,1690],{"class":925,"line":942},[923,1691,1692],{},"  projectId: 'your-project-id',\n",[923,1694,1695],{"class":925,"line":948},[923,1696,1697],{},"  firestore: { rules: fs.readFileSync('firestore.rules', 'utf8') }\n",[923,1699,1700],{"class":925,"line":954},[923,1701,1322],{},[923,1703,1704],{"class":925,"line":960},[923,1705,1070],{"emptyLinePlaceholder":341},[923,1707,1708],{"class":925,"line":966},[923,1709,1710],{},"\u002F\u002F Test: authenticated user can read their own document\n",[923,1712,1713],{"class":925,"line":1092},[923,1714,1715],{},"const userContext = testEnv.authenticatedContext('user123');\n",[923,1717,1718],{"class":925,"line":1097},[923,1719,1720],{},"await assertSucceeds(\n",[923,1722,1723],{"class":925,"line":1103},[923,1724,1725],{},"  userContext.firestore().collection('users').doc('user123').get()\n",[923,1727,1728],{"class":925,"line":1109},[923,1729,1730],{},");\n",[923,1732,1733],{"class":925,"line":1115},[923,1734,1070],{"emptyLinePlaceholder":341},[923,1736,1737],{"class":925,"line":1121},[923,1738,1739],{},"\u002F\u002F Test: user cannot read another user's document\n",[923,1741,1742],{"class":925,"line":1127},[923,1743,1744],{},"await assertFails(\n",[923,1746,1747],{"class":925,"line":1133},[923,1748,1749],{},"  userContext.firestore().collection('users').doc('other-user').get()\n",[923,1751,1752],{"class":925,"line":1139},[923,1753,1730],{},[923,1755,1756],{"class":925,"line":1144},[923,1757,1070],{"emptyLinePlaceholder":341},[923,1759,1760],{"class":925,"line":1149},[923,1761,1762],{},"\u002F\u002F Test: unauthenticated user cannot write\n",[923,1764,1765],{"class":925,"line":1155},[923,1766,1767],{},"const unauthedContext = testEnv.unauthenticatedContext();\n",[923,1769,1770],{"class":925,"line":1161},[923,1771,1744],{},[923,1773,1774],{"class":925,"line":1167},[923,1775,1776],{},"  unauthedContext.firestore().collection('users').doc('user123').set({ name: 'test' })\n",[923,1778,1779],{"class":925,"line":1173},[923,1780,1730],{},[14,1782,1783],{},"Deployment workflow:",[160,1785,1786,1801,1807,1815,1821],{},[131,1787,1788,1791,1792,700,1795,1210,1798],{},[17,1789,1790],{},"Write rules locally"," in ",[116,1793,1794],{},"firestore.rules",[116,1796,1797],{},"database.rules.json",[116,1799,1800],{},"storage.rules",[131,1802,1803,1806],{},[17,1804,1805],{},"Test with the emulator",": run the full test suite against the local emulator before every deployment",[131,1808,1809,1814],{},[17,1810,1228,1811],{},[116,1812,1813],{},"firebase deploy --only firestore:rules"," to deploy rules independently from other Firebase resources",[131,1816,1817,1820],{},[17,1818,1819],{},"Version control your rules files",": commit them to Git and review changes in pull requests",[131,1822,1823,1826],{},[17,1824,1825],{},"Monitor denied requests"," in the Firebase Console to identify legitimate access patterns blocked by overly restrictive rules",[31,1828,1830],{"id":1829},"server-side-validation-with-cloud-functions","Server-Side Validation with Cloud Functions",[14,1832,1833],{},"Security Rules handle authorization and basic data validation, but complex business logic requires server-side enforcement through Cloud Functions. Cloud Functions execute in a trusted environment where the code cannot be inspected or modified by clients.",[14,1835,1836],{},"Use Cloud Functions as a second validation layer for:",[14,1838,1839,1842],{},[17,1840,1841],{},"Financial transactions",": verify amounts, check account balances, and enforce rate limits server-side:",[915,1844,1846],{"className":917,"code":1845,"language":919,"meta":300,"style":300},"exports.processPayment = functions.https.onCall(async (data, context) => {\n  \u002F\u002F Verify authentication\n  if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');\n\n  \u002F\u002F Server-side amount validation\n  if (data.amount \u003C= 0 || data.amount > 10000) {\n    throw new functions.https.HttpsError('invalid-argument', 'Invalid amount');\n  }\n\n  \u002F\u002F Check user's balance from a trusted source\n  const userDoc = await admin.firestore().collection('accounts').doc(context.auth.uid).get();\n  if (userDoc.data().balance \u003C data.amount) {\n    throw new functions.https.HttpsError('failed-precondition', 'Insufficient balance');\n  }\n\n  \u002F\u002F Process the transaction with admin privileges\n  \u002F\u002F ...\n});\n",[116,1847,1848,1853,1858,1863,1867,1872,1877,1882,1886,1890,1895,1900,1905,1910,1914,1918,1923,1928],{"__ignoreMap":300},[923,1849,1850],{"class":925,"line":926},[923,1851,1852],{},"exports.processPayment = functions.https.onCall(async (data, context) => {\n",[923,1854,1855],{"class":925,"line":301},[923,1856,1857],{},"  \u002F\u002F Verify authentication\n",[923,1859,1860],{"class":925,"line":306},[923,1861,1862],{},"  if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');\n",[923,1864,1865],{"class":925,"line":942},[923,1866,1070],{"emptyLinePlaceholder":341},[923,1868,1869],{"class":925,"line":948},[923,1870,1871],{},"  \u002F\u002F Server-side amount validation\n",[923,1873,1874],{"class":925,"line":954},[923,1875,1876],{},"  if (data.amount \u003C= 0 || data.amount > 10000) {\n",[923,1878,1879],{"class":925,"line":960},[923,1880,1881],{},"    throw new functions.https.HttpsError('invalid-argument', 'Invalid amount');\n",[923,1883,1884],{"class":925,"line":966},[923,1885,963],{},[923,1887,1888],{"class":925,"line":1092},[923,1889,1070],{"emptyLinePlaceholder":341},[923,1891,1892],{"class":925,"line":1097},[923,1893,1894],{},"  \u002F\u002F Check user's balance from a trusted source\n",[923,1896,1897],{"class":925,"line":1103},[923,1898,1899],{},"  const userDoc = await admin.firestore().collection('accounts').doc(context.auth.uid).get();\n",[923,1901,1902],{"class":925,"line":1109},[923,1903,1904],{},"  if (userDoc.data().balance \u003C data.amount) {\n",[923,1906,1907],{"class":925,"line":1115},[923,1908,1909],{},"    throw new functions.https.HttpsError('failed-precondition', 'Insufficient balance');\n",[923,1911,1912],{"class":925,"line":1121},[923,1913,963],{},[923,1915,1916],{"class":925,"line":1127},[923,1917,1070],{"emptyLinePlaceholder":341},[923,1919,1920],{"class":925,"line":1133},[923,1921,1922],{},"  \u002F\u002F Process the transaction with admin privileges\n",[923,1924,1925],{"class":925,"line":1139},[923,1926,1927],{},"  \u002F\u002F ...\n",[923,1929,1930],{"class":925,"line":1144},[923,1931,1322],{},[14,1933,1934,1937],{},[17,1935,1936],{},"Data aggregation and denormalization",": use Firestore triggers to maintain counts, totals, and derived fields that users should not control directly:",[915,1939,1941],{"className":917,"code":1940,"language":919,"meta":300,"style":300},"exports.onPostCreated = functions.firestore\n  .document('posts\u002F{postId}')\n  .onCreate(async (snap, context) => {\n    \u002F\u002F Increment the author's post count atomically\n    const authorId = snap.data().authorId;\n    await admin.firestore().collection('users').doc(authorId).update({\n      postCount: admin.firestore.FieldValue.increment(1)\n    });\n  });\n",[116,1942,1943,1948,1953,1958,1963,1968,1973,1978,1983],{"__ignoreMap":300},[923,1944,1945],{"class":925,"line":926},[923,1946,1947],{},"exports.onPostCreated = functions.firestore\n",[923,1949,1950],{"class":925,"line":301},[923,1951,1952],{},"  .document('posts\u002F{postId}')\n",[923,1954,1955],{"class":925,"line":306},[923,1956,1957],{},"  .onCreate(async (snap, context) => {\n",[923,1959,1960],{"class":925,"line":942},[923,1961,1962],{},"    \u002F\u002F Increment the author's post count atomically\n",[923,1964,1965],{"class":925,"line":948},[923,1966,1967],{},"    const authorId = snap.data().authorId;\n",[923,1969,1970],{"class":925,"line":954},[923,1971,1972],{},"    await admin.firestore().collection('users').doc(authorId).update({\n",[923,1974,1975],{"class":925,"line":960},[923,1976,1977],{},"      postCount: admin.firestore.FieldValue.increment(1)\n",[923,1979,1980],{"class":925,"line":966},[923,1981,1982],{},"    });\n",[923,1984,1985],{"class":925,"line":1092},[923,1986,1987],{},"  });\n",[14,1989,1990,1993],{},[17,1991,1992],{},"Rate limiting",": prevent abuse by tracking request frequency per user:",[915,1995,1997],{"className":917,"code":1996,"language":919,"meta":300,"style":300},"exports.submitFeedback = functions.https.onCall(async (data, context) => {\n  const userId = context.auth.uid;\n  const lastSubmission = await admin.firestore().collection('rateLimits').doc(userId).get();\n\n  if (lastSubmission.exists) {\n    const lastTime = lastSubmission.data().timestamp.toMillis();\n    if (Date.now() - lastTime \u003C 60000) { \u002F\u002F 1 minute cooldown\n      throw new functions.https.HttpsError('resource-exhausted', 'Please wait before submitting again');\n    }\n  }\n\n  \u002F\u002F Process the submission...\n});\n",[116,1998,1999,2004,2009,2014,2018,2023,2028,2033,2038,2042,2046,2050,2055],{"__ignoreMap":300},[923,2000,2001],{"class":925,"line":926},[923,2002,2003],{},"exports.submitFeedback = functions.https.onCall(async (data, context) => {\n",[923,2005,2006],{"class":925,"line":301},[923,2007,2008],{},"  const userId = context.auth.uid;\n",[923,2010,2011],{"class":925,"line":306},[923,2012,2013],{},"  const lastSubmission = await admin.firestore().collection('rateLimits').doc(userId).get();\n",[923,2015,2016],{"class":925,"line":942},[923,2017,1070],{"emptyLinePlaceholder":341},[923,2019,2020],{"class":925,"line":948},[923,2021,2022],{},"  if (lastSubmission.exists) {\n",[923,2024,2025],{"class":925,"line":954},[923,2026,2027],{},"    const lastTime = lastSubmission.data().timestamp.toMillis();\n",[923,2029,2030],{"class":925,"line":960},[923,2031,2032],{},"    if (Date.now() - lastTime \u003C 60000) { \u002F\u002F 1 minute cooldown\n",[923,2034,2035],{"class":925,"line":966},[923,2036,2037],{},"      throw new functions.https.HttpsError('resource-exhausted', 'Please wait before submitting again');\n",[923,2039,2040],{"class":925,"line":1092},[923,2041,957],{},[923,2043,2044],{"class":925,"line":1097},[923,2045,963],{},[923,2047,2048],{"class":925,"line":1103},[923,2049,1070],{"emptyLinePlaceholder":341},[923,2051,2052],{"class":925,"line":1109},[923,2053,2054],{},"  \u002F\u002F Process the submission...\n",[923,2056,2057],{"class":925,"line":1115},[923,2058,1322],{},[14,2060,2061],{},"Cloud Functions complement Security Rules: they do not replace them. Rules should still enforce basic authentication and authorization checks. Functions handle the logic that rules cannot express.",[31,2063,2065],{"id":2064},"how-app369-secures-firebase-applications","How App369 Secures Firebase Applications",[14,2067,2068,2071],{},[69,2069,846],{"href":2070},"\u002Fservice\u002Fflutter-development\u002F"," implements Firebase security as a standard component of every project that uses the Firebase stack:",[128,2073,2074,2080,2086,2092,2098,2104],{},[131,2075,2076,2079],{},[17,2077,2078],{},"Locked-mode defaults"," on all new projects, with permissions added per-collection based on documented access requirements",[131,2081,2082,2085],{},[17,2083,2084],{},"Custom claims-based RBAC"," for multi-role applications, with claim management handled exclusively through admin-level Cloud Functions",[131,2087,2088,2091],{},[17,2089,2090],{},"Comprehensive data validation"," in rules for every writable collection: type checking, field constraints, and immutability guards",[131,2093,2094,2097],{},[17,2095,2096],{},"Automated rules testing"," in CI\u002FCD pipelines using the Firebase Emulator Suite, running the full test suite before every deployment",[131,2099,2100,2103],{},[17,2101,2102],{},"Server-side business logic"," via Cloud Functions for financial operations, rate limiting, and any validation that depends on data the client should not access",[131,2105,2106,2109,2110,2115],{},[17,2107,2108],{},"Security audits"," against the ",[69,2111,2114],{"href":2112,"rel":2113},"https:\u002F\u002Fowasp.org\u002Fwww-project-mobile-top-10\u002F",[73],"OWASP Mobile Top 10"," before production release, with specific attention to Firebase configuration",[14,2117,2118,2119,2122,2123,298],{},"App369 builds ",[69,2120,2121],{"href":2070},"Flutter applications"," with Firebase backends that meet enterprise security requirements. For projects with additional security needs, read the companion guide on ",[69,2124,2126],{"href":2125},"\u002Fblog\u002Fflutter-security-best-practices-2026\u002F","Flutter security best practices",[31,2128,2130],{"id":2129},"faq","FAQ",[108,2132,2134],{"id":2133},"what-happens-if-i-deploy-firebase-with-test-mode-rules","What happens if I deploy Firebase with test mode rules?",[14,2136,2137,2138,2142],{},"Any user on the internet can read, modify, and delete every document in your database. Test mode grants unrestricted public access using a time-based condition: before the expiry date, anyone with your project ID (which is public) has full access. According to ",[69,2139,2141],{"href":836,"rel":2140},[73],"AuditYourApp",", roughly 30% of Firebase apps in production still run with overly permissive rules. Always start from locked mode and add permissions explicitly.",[108,2144,2146],{"id":2145},"can-firebase-security-rules-replace-server-side-validation","Can Firebase Security Rules replace server-side validation?",[14,2148,2149],{},"Partially. Security Rules can enforce authentication, field-level authorization, type checking, string length constraints, and field immutability. They cannot perform cross-document queries, call external APIs, enforce complex business logic, or implement rate limiting. Use Cloud Functions for operations that exceed what rules can express, and use rules as the first layer of defense for every request.",[108,2151,2153],{"id":2152},"how-do-i-implement-admin-only-access-in-firebase","How do I implement admin-only access in Firebase?",[14,2155,2156,2157,2160,2161,2164,2165,2168],{},"Use Firebase Authentication custom claims. Set ",[116,2158,2159],{},"{ admin: true }"," on a user's account via the Admin SDK in a Cloud Function, then reference ",[116,2162,2163],{},"request.auth.token.admin == true"," in your Security Rules. Do not use a Firestore field like ",[116,2166,2167],{},"users\u002F{userId}.isAdmin"," for authorization: clients can modify their own documents if write rules are misconfigured. Custom claims are set server-side and cannot be modified by clients.",[108,2170,2172],{"id":2171},"how-often-should-i-update-firebase-security-rules","How often should I update Firebase Security Rules?",[14,2174,2175,2176,2180],{},"Review and update rules whenever you add a new collection, modify a data schema, or change role definitions. Include rules files in version control and require pull request reviews for all changes. Run the Firebase Emulator test suite in CI before every deployment. At minimum, audit your rules quarterly against the current ",[69,2177,2179],{"href":860,"rel":2178},[73],"Firebase Security Rules documentation"," to ensure they follow updated best practices.",[2182,2183,2184],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":300,"searchDepth":301,"depth":301,"links":2186},[2187,2188,2189,2190,2191,2192,2193,2194,2195],{"id":850,"depth":301,"text":851},{"id":892,"depth":301,"text":893},{"id":1035,"depth":301,"text":1036},{"id":1256,"depth":301,"text":1257},{"id":1416,"depth":301,"text":1417},{"id":1612,"depth":301,"text":1613},{"id":1829,"depth":301,"text":1830},{"id":2064,"depth":301,"text":2065},{"id":2129,"depth":301,"text":2130,"children":2196},[2197,2198,2199,2200],{"id":2133,"depth":306,"text":2134},{"id":2145,"depth":306,"text":2146},{"id":2152,"depth":306,"text":2153},{"id":2171,"depth":306,"text":2172},"2026-03-07T10:00:00.000Z","Firebase security rules complete guide for 2026: Firestore rules, role-based access control, data validation, and Cloud Functions for server-side protection.",{"readingTimeStats":2204},{"text":2205,"minutes":2206,"time":2207,"words":2208},"11 min read",10.305,618300,2061,"\u002Fblog\u002Ffirebase-security-guide-2026","11 min",[2212,2213,2214],"supabase-permission-denied-new-tables-october-30-2026","ai-assisted-app-development-guide-2026","app-maintenance-cost-guide-2026",{"title":823,"description":2202},"blog\u002Ffirebase-security-guide-2026","firebase security rules, firestore security, firebase authentication, cloud functions, role-based access control, firebase guide","2026-10-12T12:00:00.000Z","https:\u002F\u002Fapp369.com\u002Fblog\u002Ffirebase-security-guide-2026\u002F","ArpiaSkjZy0lmwYjm3PwPtDCC-iwUHgE18ym-joXZaA",{"id":2222,"title":2223,"author":2224,"body":2226,"category":310,"createdAt":2201,"description":2982,"disclosure":313,"extension":314,"faq":313,"howTo":313,"location":334,"meta":2983,"navigation":341,"ogImage":313,"path":2989,"primaryKeyword":313,"readingTime":2990,"related":313,"seo":2991,"sources":313,"stem":2992,"tags":2993,"takeaways":313,"updatedAt":2201,"url":2994,"__hash__":2995},"blog\u002Fblog\u002Fflutter-security-best-practices-2026.md","Flutter Security Best Practices: Protecting Your App in 2026",{"name":7,"social":2225},{"twitter":9},{"type":11,"value":2227,"toc":2966},[2228,2239,2245,2249,2258,2261,2268,2272,2291,2294,2357,2360,2383,2391,2395,2404,2407,2437,2440,2449,2452,2472,2479,2483,2491,2494,2503,2517,2546,2556,2559,2563,2566,2577,2583,2631,2634,2664,2668,2676,2707,2717,2720,2752,2759,2763,2766,2776,2824,2827,2855,2858,2862,2867,2908,2913,2915,2919,2932,2936,2942,2946,2952,2956,2964],[14,2229,2230,2233,2234,2238],{},[17,2231,2232],{},"Flutter security requires deliberate architecture decisions at every layer of the app",", from how credentials are stored on-device to how data moves across the network. The ",[69,2235,2237],{"href":2112,"rel":2236},[73],"OWASP Mobile Top 10 (2024)"," identifies insecure data storage, insufficient cryptography, and inadequate authentication as the most exploited vulnerabilities in production mobile apps. Flutter's single-codebase model means a security flaw ships to both iOS and Android simultaneously, doubling the exposure surface.",[14,2240,2241,2242,2244],{},"This guide covers the specific techniques, packages, and build flags that ",[69,2243,846],{"href":2070}," uses to harden Flutter applications before they reach production.",[31,2246,2248],{"id":2247},"why-flutter-apps-need-a-security-first-approach","Why Flutter Apps Need a Security-First Approach",[14,2250,2251,2252,2257],{},"Flutter compiles to native ARM code and does not expose raw source the way JavaScript-based frameworks do, but this does not make it secure by default. According to ",[69,2253,2256],{"href":2254,"rel":2255},"https:\u002F\u002Fdocs.flutter.dev\u002Fsecurity",[73],"Flutter's official security documentation",", the framework delegates sensitive operations, keychain access, certificate validation, biometric enrollment, to platform-level APIs. Developers are responsible for invoking those APIs correctly.",[14,2259,2260],{},"A misconfigured Firebase instance, an API key committed to version control, or a missing SSL pin can each independently compromise an entire user base. Security must be addressed in code architecture, not bolted on after feature development is complete.",[14,2262,2263,2264,2267],{},"The ",[69,2265,2237],{"href":2112,"rel":2266},[73]," provides the standard framework for assessing mobile app risk. The top three categories, Improper Credential Usage, Inadequate Supply Chain Security, and Insecure Authentication\u002FAuthorization, are all directly relevant to Flutter applications.",[31,2269,2271],{"id":2270},"secure-data-storage-with-flutter_secure_storage","Secure Data Storage with flutter_secure_storage",[14,2273,2274,2275,2278,2279,2286,2287,298],{},"Storing sensitive data in ",[116,2276,2277],{},"SharedPreferences"," or plain SQLite databases exposes it to any process with device-level access. The ",[69,2280,2283],{"href":2281,"rel":2282},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_secure_storage",[73],[116,2284,2285],{},"flutter_secure_storage"," package solves this by writing to the iOS Keychain and Android EncryptedSharedPreferences (backed by the Android Keystore system), as documented on ",[69,2288,2290],{"href":2281,"rel":2289},[73],"pub.dev",[14,2292,2293],{},"Here is the correct pattern for storing and retrieving tokens:",[915,2295,2299],{"className":2296,"code":2297,"language":2298,"meta":300,"style":300},"language-dart shiki shiki-themes github-light github-dark","import 'package:flutter_secure_storage\u002Fflutter_secure_storage.dart';\n\nfinal storage = FlutterSecureStorage();\n\n\u002F\u002F Store a token\nawait storage.write(key: 'auth_token', value: tokenValue);\n\n\u002F\u002F Retrieve a token\nfinal token = await storage.read(key: 'auth_token');\n\n\u002F\u002F Delete on logout\nawait storage.delete(key: 'auth_token');\n","dart",[116,2300,2301,2306,2310,2315,2319,2324,2329,2333,2338,2343,2347,2352],{"__ignoreMap":300},[923,2302,2303],{"class":925,"line":926},[923,2304,2305],{},"import 'package:flutter_secure_storage\u002Fflutter_secure_storage.dart';\n",[923,2307,2308],{"class":925,"line":301},[923,2309,1070],{"emptyLinePlaceholder":341},[923,2311,2312],{"class":925,"line":306},[923,2313,2314],{},"final storage = FlutterSecureStorage();\n",[923,2316,2317],{"class":925,"line":942},[923,2318,1070],{"emptyLinePlaceholder":341},[923,2320,2321],{"class":925,"line":948},[923,2322,2323],{},"\u002F\u002F Store a token\n",[923,2325,2326],{"class":925,"line":954},[923,2327,2328],{},"await storage.write(key: 'auth_token', value: tokenValue);\n",[923,2330,2331],{"class":925,"line":960},[923,2332,1070],{"emptyLinePlaceholder":341},[923,2334,2335],{"class":925,"line":966},[923,2336,2337],{},"\u002F\u002F Retrieve a token\n",[923,2339,2340],{"class":925,"line":1092},[923,2341,2342],{},"final token = await storage.read(key: 'auth_token');\n",[923,2344,2345],{"class":925,"line":1097},[923,2346,1070],{"emptyLinePlaceholder":341},[923,2348,2349],{"class":925,"line":1103},[923,2350,2351],{},"\u002F\u002F Delete on logout\n",[923,2353,2354],{"class":925,"line":1109},[923,2355,2356],{},"await storage.delete(key: 'auth_token');\n",[14,2358,2359],{},"Key implementation rules:",[128,2361,2362,2368,2377],{},[131,2363,2364,2367],{},[17,2365,2366],{},"Never store tokens in SharedPreferences."," SharedPreferences writes to an unencrypted XML file on Android.",[131,2369,2370,1196,2373,2376],{},[17,2371,2372],{},"Set access control on iOS.",[116,2374,2375],{},"IOSOptions(accessibility: KeychainAccessibility.first_unlock)"," to prevent access before the first device unlock.",[131,2378,2379,2382],{},[17,2380,2381],{},"Wipe storage on app uninstall detection."," On Android, Keystore entries persist after uninstall. Check for a sentinel value on first launch and clear stale credentials.",[14,2384,857,2385,2390],{},[69,2386,2389],{"href":2387,"rel":2388},"https:\u002F\u002Ftouchlane.com\u002F5-overlooked-flutter-security-vulnerabilities-and-how-to-address-them\u002F",[73],"Touchlane's security analysis",", insecure local storage remains one of the five most overlooked Flutter vulnerabilities in production apps.",[31,2392,2394],{"id":2393},"api-key-protection-and-environment-variables","API Key Protection and Environment Variables",[14,2396,2397,2398,2403],{},"Hardcoding API keys in Dart source files is the most common security mistake in Flutter projects. Even with code obfuscation, string literals can be extracted from compiled binaries using basic reverse-engineering tools. According to ",[69,2399,2402],{"href":2400,"rel":2401},"https:\u002F\u002Fhackernoon.com\u002F10-best-practices-for-securing-your-flutter-mobile-app-in-2025",[73],"HackerNoon's Flutter security guide",", exposed API keys account for a significant share of reported mobile app breaches.",[14,2405,2406],{},"Flutter provides a built-in mechanism for compile-time variable injection:",[915,2408,2410],{"className":1638,"code":2409,"language":1640,"meta":300,"style":300},"flutter build apk --dart-define=API_KEY=your_key_here\nflutter build ios --dart-define=API_KEY=your_key_here\n",[116,2411,2412,2426],{"__ignoreMap":300},[923,2413,2414,2417,2420,2423],{"class":925,"line":926},[923,2415,2416],{"class":1647},"flutter",[923,2418,2419],{"class":1651}," build",[923,2421,2422],{"class":1651}," apk",[923,2424,2425],{"class":1655}," --dart-define=API_KEY=your_key_here\n",[923,2427,2428,2430,2432,2435],{"class":925,"line":301},[923,2429,2416],{"class":1647},[923,2431,2419],{"class":1651},[923,2433,2434],{"class":1651}," ios",[923,2436,2425],{"class":1655},[14,2438,2439],{},"Access the value in Dart:",[915,2441,2443],{"className":2296,"code":2442,"language":2298,"meta":300,"style":300},"const apiKey = String.fromEnvironment('API_KEY');\n",[116,2444,2445],{"__ignoreMap":300},[923,2446,2447],{"class":925,"line":926},[923,2448,2442],{},[14,2450,2451],{},"This approach keeps keys out of version control and out of the source tree entirely. For additional protection:",[128,2453,2454,2460,2466],{},[131,2455,2456,2459],{},[17,2457,2458],{},"Use a backend proxy."," Route API calls through your own server so third-party keys never reach the client.",[131,2461,2462,2465],{},[17,2463,2464],{},"Scope keys narrowly."," Use API key restrictions (IP allowlists, bundle ID restrictions) provided by services like Google Cloud and Firebase.",[131,2467,2468,2471],{},[17,2469,2470],{},"Rotate keys on a schedule."," Automate key rotation in CI\u002FCD so compromised keys have a limited exposure window.",[14,2473,2474,2475,2478],{},"For projects that use multiple environments (development, staging, production), maintain separate ",[116,2476,2477],{},".env"," files and inject them via the build pipeline, never commit them to Git.",[31,2480,2482],{"id":2481},"authentication-oauth-20-biometrics-and-mfa","Authentication: OAuth 2.0, Biometrics, and MFA",[14,2484,2485,2486,298],{},"Proper authentication combines secure token management with multi-factor verification. OAuth 2.0 and OpenID Connect remain the standard protocols for mobile authentication in 2026, as documented by the ",[69,2487,2490],{"href":2488,"rel":2489},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc8252",[73],"OAuth 2.0 for Mobile and Native Apps RFC (RFC 8252)",[14,2492,2493],{},"Implementation requirements for Flutter apps:",[14,2495,2496,2499,2500,2502],{},[17,2497,2498],{},"Short-lived access tokens with refresh tokens."," Access tokens should expire within 15-60 minutes. Use refresh tokens stored in ",[116,2501,2285],{}," to obtain new access tokens without requiring re-authentication. This limits the damage window if a token is intercepted.",[14,2504,2505,2508,2509,2516],{},[17,2506,2507],{},"Biometric authentication as a second factor."," The ",[69,2510,2513],{"href":2511,"rel":2512},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Flocal_auth",[73],[116,2514,2515],{},"local_auth"," package provides fingerprint and face recognition support on both platforms. Use it to gate sensitive operations (payments, profile changes) rather than as a standalone login method.",[915,2518,2520],{"className":2296,"code":2519,"language":2298,"meta":300,"style":300},"final localAuth = LocalAuthentication();\nfinal authenticated = await localAuth.authenticate(\n  localizedReason: 'Verify your identity to continue',\n  options: const AuthenticationOptions(biometricOnly: true),\n);\n",[116,2521,2522,2527,2532,2537,2542],{"__ignoreMap":300},[923,2523,2524],{"class":925,"line":926},[923,2525,2526],{},"final localAuth = LocalAuthentication();\n",[923,2528,2529],{"class":925,"line":301},[923,2530,2531],{},"final authenticated = await localAuth.authenticate(\n",[923,2533,2534],{"class":925,"line":306},[923,2535,2536],{},"  localizedReason: 'Verify your identity to continue',\n",[923,2538,2539],{"class":925,"line":942},[923,2540,2541],{},"  options: const AuthenticationOptions(biometricOnly: true),\n",[923,2543,2544],{"class":925,"line":948},[923,2545,1730],{},[14,2547,2548,2551,2552,2555],{},[17,2549,2550],{},"Multi-factor authentication (MFA)."," Combine password-based login with either biometric verification or a TOTP (Time-Based One-Time Password) code. Firebase Authentication supports MFA enrollment natively through the ",[116,2553,2554],{},"firebase_auth"," package.",[14,2557,2558],{},"Never store passwords locally, even in encrypted form. Delegate credential verification entirely to the authentication server.",[31,2560,2562],{"id":2561},"network-security-ssl-pinning-and-certificate-validation","Network Security: SSL Pinning and Certificate Validation",[14,2564,2565],{},"SSL\u002FTLS encryption protects data in transit, but standard HTTPS alone does not prevent man-in-the-middle (MITM) attacks on compromised networks. SSL pinning ties your app to a specific server certificate or public key, rejecting connections even if a trusted certificate authority has been compromised.",[14,2567,857,2568,2573,2574,298],{},[69,2569,2572],{"href":2570,"rel":2571},"https:\u002F\u002Fsolguruz.com\u002Fblog\u002Fflutter-security-best-practices\u002F",[73],"SolGuruz's Flutter security analysis",", SSL pinning is one of the most effective defenses against MITM attacks on ",[69,2575,2576],{"href":845},"mobile applications",[14,2578,2579,2580,1668],{},"Implementation with the ",[116,2581,2582],{},"http_certificate_pinning",[915,2584,2586],{"className":2296,"code":2585,"language":2298,"meta":300,"style":300},"import 'package:http_certificate_pinning\u002Fhttp_certificate_pinning.dart';\n\nfinal result = await HttpCertificatePinning.check(\n  serverURL: 'https:\u002F\u002Fapi.yourapp.com',\n  headerHttp: {},\n  sha: SHA.SHA256,\n  allowedSHAFingerprints: ['YOUR_CERTIFICATE_SHA256_FINGERPRINT'],\n  timeout: 50,\n);\n",[116,2587,2588,2593,2597,2602,2607,2612,2617,2622,2627],{"__ignoreMap":300},[923,2589,2590],{"class":925,"line":926},[923,2591,2592],{},"import 'package:http_certificate_pinning\u002Fhttp_certificate_pinning.dart';\n",[923,2594,2595],{"class":925,"line":301},[923,2596,1070],{"emptyLinePlaceholder":341},[923,2598,2599],{"class":925,"line":306},[923,2600,2601],{},"final result = await HttpCertificatePinning.check(\n",[923,2603,2604],{"class":925,"line":942},[923,2605,2606],{},"  serverURL: 'https:\u002F\u002Fapi.yourapp.com',\n",[923,2608,2609],{"class":925,"line":948},[923,2610,2611],{},"  headerHttp: {},\n",[923,2613,2614],{"class":925,"line":954},[923,2615,2616],{},"  sha: SHA.SHA256,\n",[923,2618,2619],{"class":925,"line":960},[923,2620,2621],{},"  allowedSHAFingerprints: ['YOUR_CERTIFICATE_SHA256_FINGERPRINT'],\n",[923,2623,2624],{"class":925,"line":966},[923,2625,2626],{},"  timeout: 50,\n",[923,2628,2629],{"class":925,"line":1092},[923,2630,1730],{},[14,2632,2633],{},"Additional network security measures:",[128,2635,2636,2652,2658],{},[131,2637,2638,2641,2642,1791,2645,2648,2649,298],{},[17,2639,2640],{},"Disable cleartext traffic."," On Android, set ",[116,2643,2644],{},"android:usesCleartextTraffic=\"false\"",[116,2646,2647],{},"AndroidManifest.xml",". On iOS, enforce App Transport Security (ATS) in ",[116,2650,2651],{},"Info.plist",[131,2653,2654,2657],{},[17,2655,2656],{},"Validate all server responses."," Check HTTP status codes and response structures before processing data. Reject unexpected content types.",[131,2659,2660,2663],{},[17,2661,2662],{},"Implement request signing."," Add HMAC signatures to API requests so the server can verify request authenticity and detect tampering.",[31,2665,2667],{"id":2666},"code-obfuscation-and-reverse-engineering-prevention","Code Obfuscation and Reverse Engineering Prevention",[14,2669,2670,2671,216],{},"Flutter's Dart code compiles to native ARM binaries, which provides more protection than interpreted code but does not prevent determined reverse engineering. The Flutter toolchain includes built-in obfuscation support, as documented on ",[69,2672,2675],{"href":2673,"rel":2674},"https:\u002F\u002Fdocs.flutter.dev\u002Fdeployment\u002Fobfuscate",[73],"Flutter's official docs",[915,2677,2679],{"className":1638,"code":2678,"language":1640,"meta":300,"style":300},"flutter build apk --obfuscate --split-debug-info=debug-info\u002F\nflutter build ios --obfuscate --split-debug-info=debug-info\u002F\n",[116,2680,2681,2695],{"__ignoreMap":300},[923,2682,2683,2685,2687,2689,2692],{"class":925,"line":926},[923,2684,2416],{"class":1647},[923,2686,2419],{"class":1651},[923,2688,2422],{"class":1651},[923,2690,2691],{"class":1655}," --obfuscate",[923,2693,2694],{"class":1655}," --split-debug-info=debug-info\u002F\n",[923,2696,2697,2699,2701,2703,2705],{"class":925,"line":301},[923,2698,2416],{"class":1647},[923,2700,2419],{"class":1651},[923,2702,2434],{"class":1651},[923,2704,2691],{"class":1655},[923,2706,2694],{"class":1655},[14,2708,2263,2709,2712,2713,2716],{},[116,2710,2711],{},"--obfuscate"," flag renames classes, methods, and fields to meaningless identifiers. The ",[116,2714,2715],{},"--split-debug-info"," flag extracts debug symbols into a separate directory, which must be stored securely for crash report symbolication but never shipped with the app.",[14,2718,2719],{},"Additional anti-reverse-engineering measures:",[128,2721,2722,2736,2742],{},[131,2723,2724,2727,2728,2735],{},[17,2725,2726],{},"Detect rooted\u002Fjailbroken devices."," Use the ",[69,2729,2732],{"href":2730,"rel":2731},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_jailbreak_detection",[73],[116,2733,2734],{},"flutter_jailbreak_detection"," package to identify compromised devices and restrict functionality accordingly.",[131,2737,2738,2741],{},[17,2739,2740],{},"Implement runtime integrity checks."," Verify that the app binary has not been modified by checking code signatures at startup.",[131,2743,2744,2747,2748,2751],{},[17,2745,2746],{},"Use ProGuard\u002FR8 on Android."," Enable minification in ",[116,2749,2750],{},"build.gradle"," to shrink and obfuscate the Java\u002FKotlin layer of your Flutter app.",[14,2753,2754,2755,2758],{},"Store the ",[116,2756,2757],{},"debug-info\u002F"," directory in a secure, access-controlled location. These files are required to decode stack traces from production crash reports.",[31,2760,2762],{"id":2761},"encryption-aes-256-and-rsa-implementation","Encryption: AES-256 and RSA Implementation",[14,2764,2765],{},"Data that must be stored or transmitted in encrypted form requires proper cryptographic implementation. AES-256 is the standard for symmetric encryption of data at rest, while RSA handles asymmetric key exchange.",[14,2767,2263,2768,2775],{},[69,2769,2772],{"href":2770,"rel":2771},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fencrypt",[73],[116,2773,2774],{},"encrypt"," package provides both AES and RSA implementations for Flutter:",[915,2777,2779],{"className":2296,"code":2778,"language":2298,"meta":300,"style":300},"import 'package:encrypt\u002Fencrypt.dart';\n\n\u002F\u002F AES-256 encryption\nfinal key = Key.fromSecureRandom(32); \u002F\u002F 256-bit key\nfinal iv = IV.fromSecureRandom(16);   \u002F\u002F Initialization vector\nfinal encrypter = Encrypter(AES(key, mode: AESMode.cbc));\n\nfinal encrypted = encrypter.encrypt('sensitive data', iv: iv);\nfinal decrypted = encrypter.decrypt(encrypted, iv: iv);\n",[116,2780,2781,2786,2790,2795,2800,2805,2810,2814,2819],{"__ignoreMap":300},[923,2782,2783],{"class":925,"line":926},[923,2784,2785],{},"import 'package:encrypt\u002Fencrypt.dart';\n",[923,2787,2788],{"class":925,"line":301},[923,2789,1070],{"emptyLinePlaceholder":341},[923,2791,2792],{"class":925,"line":306},[923,2793,2794],{},"\u002F\u002F AES-256 encryption\n",[923,2796,2797],{"class":925,"line":942},[923,2798,2799],{},"final key = Key.fromSecureRandom(32); \u002F\u002F 256-bit key\n",[923,2801,2802],{"class":925,"line":948},[923,2803,2804],{},"final iv = IV.fromSecureRandom(16);   \u002F\u002F Initialization vector\n",[923,2806,2807],{"class":925,"line":954},[923,2808,2809],{},"final encrypter = Encrypter(AES(key, mode: AESMode.cbc));\n",[923,2811,2812],{"class":925,"line":960},[923,2813,1070],{"emptyLinePlaceholder":341},[923,2815,2816],{"class":925,"line":966},[923,2817,2818],{},"final encrypted = encrypter.encrypt('sensitive data', iv: iv);\n",[923,2820,2821],{"class":925,"line":1092},[923,2822,2823],{},"final decrypted = encrypter.decrypt(encrypted, iv: iv);\n",[14,2825,2826],{},"Implementation rules:",[128,2828,2829,2835,2841,2849],{},[131,2830,2831,2834],{},[17,2832,2833],{},"Never reuse initialization vectors (IVs)."," Generate a new random IV for each encryption operation. Reusing IVs with the same key allows attackers to derive plaintext.",[131,2836,2837,2840],{},[17,2838,2839],{},"Use AES-256-CBC or AES-256-GCM."," GCM mode provides both encryption and authentication, detecting tampering automatically. Prefer GCM when available.",[131,2842,2843,1196,2846,2848],{},[17,2844,2845],{},"Store encryption keys in the platform keystore.",[116,2847,2285],{}," to persist keys, never embed them in source code or configuration files.",[131,2850,2851,2854],{},[17,2852,2853],{},"Use RSA for key exchange only."," RSA is computationally expensive and has message size limits. Use it to encrypt and exchange AES session keys, then use AES for the actual data encryption.",[14,2856,2857],{},"For applications handling financial or health data, consider using the platform's native cryptographic APIs through method channels for FIPS 140-2 compliance.",[31,2859,2861],{"id":2860},"how-app369-secures-flutter-applications","How App369 Secures Flutter Applications",[14,2863,2864,2866],{},[69,2865,846],{"href":2070}," applies these security practices as standard procedure across every Flutter project:",[128,2868,2869,2875,2881,2887,2893,2899],{},[131,2870,2871,2874],{},[17,2872,2873],{},"Security architecture review"," before development begins, mapping data flows and identifying threat surfaces specific to the application's domain",[131,2876,2877,2880],{},[17,2878,2879],{},"Automated dependency scanning"," in CI\u002FCD pipelines to flag packages with known vulnerabilities before they reach production",[131,2882,2883,2886],{},[17,2884,2885],{},"Mandatory code obfuscation"," on all release builds, with debug symbols stored in access-controlled cloud storage",[131,2888,2889,2892],{},[17,2890,2891],{},"SSL pinning"," configured for all API endpoints, with certificate rotation procedures documented in runbooks",[131,2894,2895,2898],{},[17,2896,2897],{},"Penetration testing"," on release candidates, using both automated tools and manual review against the OWASP Mobile Top 10",[131,2900,2901,2904,2905,2907],{},[17,2902,2903],{},"Secure storage enforcement",": all tokens, keys, and user credentials stored exclusively through ",[116,2906,2285],{},", never in SharedPreferences or plain databases",[14,2909,2118,2910,2912],{},[69,2911,2576],{"href":845}," with security requirements ranging from startup MVPs to enterprise applications handling regulated financial data.",[31,2914,2130],{"id":2129},[108,2916,2918],{"id":2917},"what-is-the-most-common-flutter-security-vulnerability","What is the most common Flutter security vulnerability?",[14,2920,2921,2922,2924,2925,2927,2928,2931],{},"Insecure data storage is the most common vulnerability. Developers frequently store authentication tokens, API keys, and user data in ",[116,2923,2277],{}," or unencrypted SQLite databases instead of using ",[116,2926,2285],{},". This exposes sensitive data to any process with root access on the device. The ",[69,2929,2237],{"href":2112,"rel":2930},[73]," lists Improper Credential Usage as the top mobile security risk.",[108,2933,2935],{"id":2934},"does-flutters-compilation-to-native-code-make-it-secure","Does Flutter's compilation to native code make it secure?",[14,2937,2938,2939,2941],{},"No. Flutter compiles Dart to native ARM code, which makes casual reverse engineering harder than with JavaScript frameworks, but it does not prevent determined attackers from decompiling the binary. String literals (including hardcoded API keys) remain extractable. Code obfuscation with ",[116,2940,2711],{}," and proper key management are still required.",[108,2943,2945],{"id":2944},"how-do-i-protect-api-keys-in-a-flutter-app","How do I protect API keys in a Flutter app?",[14,2947,1228,2948,2951],{},[116,2949,2950],{},"--dart-define"," to inject keys at compile time instead of hardcoding them in source files. For stronger protection, route API calls through a backend proxy server so third-party keys never exist on the client device. Apply API key restrictions (bundle ID, IP allowlists) on the service provider side, and rotate keys regularly through your CI\u002FCD pipeline.",[108,2953,2955],{"id":2954},"is-ssl-pinning-necessary-for-flutter-apps","Is SSL pinning necessary for Flutter apps?",[14,2957,2958,2959,2963],{},"Yes, if your app transmits sensitive data. Standard HTTPS validates that the server has a certificate signed by a trusted authority, but it does not prevent MITM attacks using a rogue CA certificate installed on the device. SSL pinning binds your app to specific certificate fingerprints, rejecting all others. According to ",[69,2960,2962],{"href":2570,"rel":2961},[73],"SolGuruz",", SSL pinning is one of the most effective network security measures for mobile applications.",[2182,2965,2184],{},{"title":300,"searchDepth":301,"depth":301,"links":2967},[2968,2969,2970,2971,2972,2973,2974,2975,2976],{"id":2247,"depth":301,"text":2248},{"id":2270,"depth":301,"text":2271},{"id":2393,"depth":301,"text":2394},{"id":2481,"depth":301,"text":2482},{"id":2561,"depth":301,"text":2562},{"id":2666,"depth":301,"text":2667},{"id":2761,"depth":301,"text":2762},{"id":2860,"depth":301,"text":2861},{"id":2129,"depth":301,"text":2130,"children":2977},[2978,2979,2980,2981],{"id":2917,"depth":306,"text":2918},{"id":2934,"depth":306,"text":2935},{"id":2944,"depth":306,"text":2945},{"id":2954,"depth":306,"text":2955},"Flutter security best practices for 2026: secure storage, API key protection, SSL pinning, code obfuscation, and AES-256 encryption explained in detail.",{"readingTimeStats":2984},{"text":2985,"minutes":2986,"time":2987,"words":2988},"8 min read",7.865,471900,1573,"\u002Fblog\u002Fflutter-security-best-practices-2026","10 min",{"title":2223,"description":2982},"blog\u002Fflutter-security-best-practices-2026","flutter security, mobile app security, flutter secure storage, SSL pinning, code obfuscation, API key protection","https:\u002F\u002Fapp369.com\u002Fblog\u002Fflutter-security-best-practices-2026\u002F","pDFoEerNjIoiNqqSEcqzWaLBF2Y89rUiyclWyHz3YTU",[2997,2999,3002,3005,3008,3009],{"keyword":5,"url":372,"title":5,"relevance":2998},75,{"keyword":3000,"url":3001,"title":3000,"relevance":1092},"Flutter vs Native in 2026: An Honest Decision Guide","https:\u002F\u002Fapp369.com\u002Fblog\u002Fflutter-vs-native-app-development-2026\u002F",{"keyword":3003,"url":3004,"title":3003,"relevance":966},"JCenter Shutdown: Fixing Old Android and Flutter Builds","https:\u002F\u002Fapp369.com\u002Fblog\u002Fjcenter-bintray-shut-down-fix-android-flutter-builds-2026\u002F",{"keyword":3006,"url":3007,"title":3006,"relevance":966},"How to Debug a Flutter and Firebase Bug: Find the Root Cause (2026)","https:\u002F\u002Fapp369.com\u002Fblog\u002Fhow-to-debug-a-flutter-firebase-bug-2026\u002F",{"keyword":377,"url":819,"title":377,"relevance":954},{"keyword":3010,"url":3011,"title":3010,"relevance":954},"Deep Linking in 2026: What Replaced Firebase Dynamic Links and How to Set It Up","https:\u002F\u002Fapp369.com\u002Fblog\u002Ffirebase-dynamic-links-shutdown-deep-linking-2026\u002F",1792307201033]