OWASP Top 10 For Flutter - M1: Mastering Credential Security in Flutter
Over the years, I have been developing applications, and mobile app security is often underestimated. Since I am a passionate Flutter and Security developer, I thought it might be helpful to share my experience and research the OWASP top 10 de-facto standards to mitigate security issues common in mobile applications. That's why I decided to write 10 articles to cover the top 10 OWASP security vulnerabilities.

Majid Hajian - Azure & AI advocate@Microsoft, Dart & Flutter community leader, Organizer@FlutterVikings, http://flutterengineering.io author
In this comprehensive guide, we explore M1—Improper Credential Usage—from the OWASP Mobile Top 10, focusing on Flutter application development, and see how you can address different scenarios and protect your apps.

What is M1: Improper Credential Usage?
Improper Credential Usage refers to improperly handling, storing, and transmitting authentication credentials, API keys, tokens, or sensitive information that can be exploited if exposed. This vulnerability often occurs when sensitive credentials are stored insecurely (for example, in plain text within the code or unencrypted local storage) or transmitted over unprotected channels.
When credentials are mismanaged, attackers can reverse-engineer mobile applications to extract these secrets, gaining unauthorized access to backend services and user data. This vulnerability is not just about weak passwords; it encompasses the entire lifecycle of credential management—from creation and storage to usage and eventual rotation.
According to the OWASP Mobile Top 10 documentation , improper credential usage is a critical risk that can lead to significant breaches, impacting user privacy and the integrity of mobile applications.
How Does Improper Credential Usage Happen?
There are multiple cases where issues may occur; let's get into each of them and learn to see if they look familiar.
Insecure Storage Practices
One common way this vulnerability manifests is through insecure storage practices. In the rush to build features, many developers might hardcode API keys or authentication tokens directly into their source code. Consider this simplified example:
The API key is embedded within the app’s code in this example. When the app is compiled, reverse-engineering techniques can be applied to extract this key. Tools exist that can decompile APKs or analyze binary code, making this a significant risk.
Stay tuned for my reverse engineering article, which is being published soon, to learn more about how you can find these keys.
Caching Sensitive Data in Memory
Another potential pitfall is caching sensitive information in memory during runtime without proper safeguards. Flutter apps may cache user tokens or other sensitive data for performance reasons. However, if this data is not managed correctly, it could be exposed through debugging tools or if the app’s memory is dumped during a crash.
For example, holding a user token in a global variable might seem convenient but can pose a risk if an attacker finds a way to inspect the app’s runtime memory.
Accidental Exposure Through Debug Logs
Even if you are careful where you store your credentials, logging them during development for debugging purposes can be dangerous. If logs are not properly sanitized or shipped to a remote logging service, sensitive data can leak through logs.
This doesn't need to be on the device itself. Sometimes, these keys are mistakenly reported to error reporting systems such as Sentry or Firebase Crashlytics or even stored on the device without encryption.
Local File Storage without Encryption
Sometimes, developers write sensitive data to local files on the device for persistence. While robust, Flutter’s file-handling capabilities do not inherently encrypt the data written to these files. Without additional encryption measures, data stored in files can be read by unauthorized apps or through direct access to the device’s file system.
Even if the credentials are not part of your source code, saving them in a file without encryption exposes them if the device is compromised.
Unencrypted Storage
Many developers turn to the shared_preferences
package for its simplicity when saving small pieces of data. However, this package stores data in plain text on the device, meaning that if an attacker gains physical access or exploits vulnerabilities in the device’s operating system, they could easily retrieve these values.
Even if you’re not embedding the credentials directly in your source code, storing them in Shared Preferences creates a risk. The storage's plain-text nature means there is no built-in encryption, making it a vulnerable point in your application.
Insecure Transmission
Even if credentials are stored securely, transmitting them over insecure channels poses another risk. The credentials could be intercepted via man-in-the-middle (MITM) attacks if data is sent over HTTP instead of HTTPS. For example, consider this network request using an insecure endpoint:
In this snippet, the absence of HTTPS means that sensitive credentials are exposed during transit, making it easier for attackers to capture them.
Code Exposure in Public Repositories
Another vector for improper credential usage is the accidental exposure of secrets in public code repositories. Developers might inadvertently push configuration files containing sensitive information to GitHub or other version control systems. Even if the repository is later made private or the keys are revoked, attackers might have already archived the credentials.

The Impact on Flutter Applications
For Flutter developers, improper credential usage can have severe implications. Let’s examine the different dimensions of impact:
Compromised User Data: When credentials are exposed, attackers can gain unauthorized access to backend services, allowing them to read, modify, or delete user data. This violates user privacy and may result in a loss of trust and potential legal consequences under data protection regulations.
Unauthorized API Access: API keys and tokens are often the gatekeepers to essential backend functionalities. If these keys are compromised, an attacker can misuse the API to perform unauthorized operations. This can lead to manipulating business logic, fraudulent transactions, or even complete service disruption.
Financial and Reputational Damage: Organizations that suffer from data breaches often face significant economic losses from direct damages and the cost of incident remediation. Additionally, the reputational damage from a security breach can be long-lasting, affecting user retention and overall business performance.
Increased Maintenance and Compliance Costs: Recovering a security breach involves substantial effort. Developers need to rotate keys, fix vulnerabilities, and possibly rebuild parts of the app. This increased workload can delay new features and lead to higher long-term maintenance costs. Moreover, non-compliance with industry standards might lead to penalties and fines.

Mitigation Strategies and Best Practices
Managing credentials securely in Flutter isn’t just about avoiding hardcoded secrets—it's about establishing a comprehensive strategy encompassing every step of the credential lifecycle. Let's see what are the possibilities.

1. Navigating Platform-Specific Storage Differences
Flutter apps run on both iOS and Android, and each platform offers its secure storage mechanisms: iOS has Keychain, and Android offers Keystore. A common challenge is ensuring your credential storage code works seamlessly across platforms without compromising security.
Use the flutter_secure_storage
package. It abstracts away the platform-specific details and allows you to store sensitive data encrypted.
This unified API ensures that whether your app runs on iOS or Android, credentials are stored using the best available security features of the respective platforms.
2. Overcoming Rapid Prototyping and Development Pressures
Under the pressure of rapid development, developers might use quick-and-dirty solutions—such as hardcoding credentials or using less secure methods shared_preferences
—to speed up prototyping. However, these shortcuts can leave your app vulnerable when it scales to production.
Even during the prototyping phase, use secure storage from the start to instill good habits. This minimizes technical debt when transitioning to production.
3. Mitigating Risks of Inadequate Developer Awareness
Developers unaware of security best practices might inadvertently expose sensitive information—for example, through debug logs or by pushing configuration files with credentials to version control.
There are two ways you can mitigate this issue:
Use Environment Variables: Instead of hardcoding credentials, manage them through environment variables using the
flutter_dotenv
package. This keeps sensitive data out of your source code.
Sanitize Logs in Production: Ensure that the debugging of sensitive information is disabled or sanitized in production builds.
4. Managing Credential Lifecycles: Rotation and Expiry
Using long-lived credentials increases the risk of exploitation. In case of compromise, implementing token rotation and short-lived credentials—such as JSON Web Tokens (JWTs)—minimizes the exposure window.
Scenario: Handling Token Refresh
The app should automatically refresh a token using a secure process when it expires.
This mechanism ensures that even if an attacker intercepts a token, its usefulness is limited by its short lifespan.
5. Enhancing Security During Data Transmission
Even with secure local storage, transmitting credentials over insecure channels exposes them to interception. All network communications should occur over HTTPS, and additional measures like certificate pinning can further enhance security.
To further protect against man-in-the-middle attacks, consider implementing certificate pinning. This additional step verifies that your app communicates only with trusted servers.
6. Code Obfuscation and Environment Separation
Another layer of defense is to make it harder for attackers to reverse engineer your app. Flutter provides support for limited code obfuscation (it is mostly just minification!) during the build process, which can help mask the logic of your code by renaming function and class names making it more challenging to navigate during reverse engineering process.
When building your Flutter app for release, you can enable obfuscation with the following command:
In addition, separating sensitive configuration from your main codebase using environment variables or external configuration files further reduces exposure. Tools like flutter_dotenv allow you to manage configuration settings without embedding them directly into the source code.
7. Integrating Runtime Application Self-Protection (RASP)
Runtime Application Self-Protection (RASP) is an assertive security technology that monitors your application in real-time to detect, alert, and even block malicious activities as they occur. RASP embeds security controls within the application to analyze its behavior during execution and respond to threats dynamically.
How RASP Can Help
Real-Time Threat Detection:
RASP continuously monitors the app’s runtime behavior to identify suspicious actions, such as abnormal API calls, repeated failed login attempts, or attempts to tamper with the app’s execution. This allows your app to react immediately to potential threats.
Automated Response:
Upon detecting a threat, RASP can trigger automated responses—like terminating a session, logging the event, or alerting a backend system—thus minimizing the window of vulnerability.
Enhanced Forensics:
By logging detailed information about runtime events, RASP can help you understand how an attack was attempted, providing valuable insights for further strengthening your app's security.
Additional Layer of Defense:
Even if vulnerabilities exist in other parts of your application, RASP serves as a last line of defense by ensuring that malicious behavior is caught and mitigated during execution.
One excellent tool for this purpose is freeRASP, a Flutter package that brings RASP capabilities directly into your project. freeRASP helps detect runtime threats, logs them, and even allows you to trigger automated responses to mitigate potential damage.
Integrating freeRASP into Your Flutter App
Integrating freeRASP is straightforward. The package provides an initialization function where you can set up a callback for when a threat is detected. Below is an example of how to integrate freeRASP in your Flutter application.
Step 1: Add freeRASP to Your Project
Add freeRASP to your pubspec.yaml
file:
Then, run flutter pub get
to install the package.
Step 2: Initialize freeRASP in Your Main Function
Set up freeRASP during the initialization phase of your application. This ensures that threat monitoring starts as soon as your app runs.
In this example, freeRASP is initialized before the app starts. The onThreatDetected
callback provides you with a threatInfo
object that contains details about the detected threat. You can use this information to log events, notify users, or take immediate action to protect your app.
Step 3: Monitor and React to Threats in Critical Areas
While freeRASP continuously monitors your app in the background, you might also want to check for threats at critical junctures—such as during user authentication or before accessing sensitive data. To perform an on-demand check, you can call freeRASP’s functions directly within your app logic.
This example demonstrates how you might integrate an additional security check before executing sensitive operations, reinforcing the protection provided by freeRASP.
Implementing a Secure Credential Management Flow
Let’s build a more complete example that shows how a Flutter app can handle secure credential management from login to token rotation. This example assumes a backend that issues a JWT and a refresh token.
Step 1: Secure Login and Token Storage
First, we need to implement a login method that securely stores both the JWT and the refresh token.
Step 2: Using the Token in API Requests
Once the JWT is stored, it can be used to authenticate further API requests. Here’s an example of how to attach the token to a request:
Step 3: Handling Token Expiry and Refresh
JWTs are designed to expire after a short period. When a token expires, the app should use the stored refresh token to request a new JWT.
Putting It All Together
Improving credential security is not about relying on a single solution; it’s about building a defense-in-depth strategy that layers multiple protective measures.

For Flutter developers, this involves:
Adopting Secure Storage: Replace insecure storage practices with robust solutions like
flutter_secure_storage
.Ensuring Secure Communications: Always use HTTPS and consider implementing certificate pinning to protect data in transit.
Implementing Token Management: Use JWTs with short lifespans and secure refresh mechanisms to minimize exposure.
Enhancing Code Security: Utilize obfuscation and environment variable management to keep sensitive data out of the codebase.
Monitoring at Runtime: Consider RASP tools to dynamically detect and respond to credential misuse.
By addressing each layer of the credential lifecycle—from storage to transmission and renewal—you create a resilient application architecture against external attacks and internal oversights.
Conclusion
M1: Improper Credential Usage poses a serious risk to mobile security, especially for Flutter apps. By leveraging secure storage, enforcing HTTPS, rotating tokens, and integrating runtime monitoring with tools like freeRASP, you can protect sensitive data and build lasting trust with your users. Embrace these best practices to create robust, secure applications, and stay tuned for the next installment in our OWASP Mobile Top 10 series, where we continue to explore actionable strategies to safeguard your mobile apps.
Last updated
Was this helpful?