Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      CodeSOD: A Unique Way to Primary Key

      July 22, 2025

      BrowserStack launches Figma plugin for detecting accessibility issues in design phase

      July 22, 2025

      Parasoft brings agentic AI to service virtualization in latest release

      July 22, 2025

      Node.js vs. Python for Backend: 7 Reasons C-Level Leaders Choose Node.js Talent

      July 21, 2025

      The best CRM software with email marketing in 2025: Expert tested and reviewed

      July 22, 2025

      This multi-port car charger can power 4 gadgets at once – and it’s surprisingly cheap

      July 22, 2025

      I’m a wearables editor and here are the 7 Pixel Watch 4 rumors I’m most curious about

      July 22, 2025

      8 ways I quickly leveled up my Linux skills – and you can too

      July 22, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      The Intersection of Agile and Accessibility – A Series on Designing for Everyone

      July 22, 2025
      Recent

      The Intersection of Agile and Accessibility – A Series on Designing for Everyone

      July 22, 2025

      Zero Trust & Cybersecurity Mesh: Your Org’s Survival Guide

      July 22, 2025

      Execute Ping Commands and Get Back Structured Data in PHP

      July 22, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      A Tomb Raider composer has been jailed — His legacy overshadowed by $75k+ in loan fraud

      July 22, 2025
      Recent

      A Tomb Raider composer has been jailed — His legacy overshadowed by $75k+ in loan fraud

      July 22, 2025

      “I don’t think I changed his mind” — NVIDIA CEO comments on H20 AI GPU sales resuming in China following a meeting with President Trump

      July 22, 2025

      Galaxy Z Fold 7 review: Six years later — Samsung finally cracks the foldable code

      July 22, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Secure Mobile APIs in Flutter

    How to Secure Mobile APIs in Flutter

    May 6, 2025

    As mobile applications continue to evolve in functionality and scope, securing the APIs that power these apps has become more critical than ever.

    In the context of Flutter, a framework that enables cross-platform development, understanding how to secure mobile APIs is essential – not only for maintaining user trust but also for safeguarding sensitive business data.

    In this article, we’ll explore common API vulnerabilities in mobile applications, particularly Flutter apps, and outline practical strategies to mitigate these risks.

    Table of Contents

    • Why API Security Matters in Mobile Apps

    • Project Setup Example:

    • Common Vulnerabilities in Flutter Apps

    • Example: Secure API Call in Flutter

    • Best Practices for Securing APIs in Flutter Apps

    • Security Checklist for Flutter Developers

    • Additional Considerations

    • Conclusion

    • References

    Securing API keys in a Flutter application is essential to prevent unauthorized access to sensitive resources. API keys are often used for authentication with external services – but if they’re exposed, they can lead to security vulnerabilities.

    In this guide, we will discuss how to securely store and manage API keys using Firebase Remote Config, Flutter Secure Storage, AES encryption, and device-specific identifiers.

    There are several ways to manage API keys securely, including:

    • CI/CD Solutions: Services like Codemagic, CircleCI, and GitHub Actions allow you to store API keys as environment variables to keep them out of your codebase.

    • Backend Storage: Keeping API keys on a backend server and fetching them dynamically is another secure approach.

    • Keystore & Keychain: On Android and iOS, API keys can be securely stored using the device’s built-in keystore mechanisms.

    • Encrypted Storage: Using encrypted local storage solutions to save API keys on the device.

    Why API Security Matters in Mobile Apps

    APIs serve as the bridge between mobile applications and backend services. While they enable dynamic experiences, such as fetching user data, processing payments, and managing real-time content, they also become a major attack vector if left unsecured.

    Mobile applications, unlike web apps, are distributed in compiled form (for example, APKs). These can be decompiled to reveal logic, endpoints, and sometimes even secrets like API keys.

    Attackers may reverse engineer APKs, intercept traffic using proxy tools like Burp Suite, or abuse API endpoints via emulators or scripts. This can lead to data breaches, unauthorized data manipulation, or service disruption.

    Publicly exposing API keys in your Flutter application can lead to unauthorized access and potential abuse. This can result in quota exhaustion, service disruptions, or even security breaches. Using Firebase Remote Config, encryption, and secure local storage, we can keep API keys safe.

    Project Setup Example:

    For this example, we will focus on using Firebase Remote Config to securely retrieve API keys, encrypt them before storing them locally, and decrypt them when needed.

    We will structure an implementation using the following:

    • remote_config.dart: Handles fetching and encrypting API keys.

    • global_config.dart: Initializes Firebase, loads environment variables, and ensures API keys are available.

    • main.dart: Starts the application and initializes configurations.

    • app_strings.dart: Stores constant values used throughout the project.

    Step 1: Setting Up Environment Variables

    Create a .env file in your Flutter project root directory and define your encryption key:

    ENCRYPTION_KEY=32-character-secure-key-here
    

    Add flutter_dotenv to your pubspec.yaml:

    dependencies:
      flutter:
        sdk: flutter
      encrypt: ^5.0.3
      flutter_dotenv: ^5.2.1
      device_info_plus: ^11.3.0
      firebase_remote_config: ^5.4.0
      flutter_secure_storage: ^9.0.0
    

    Run:

    flutter pub get
    

    Step 2: Secure Storage and Encryption

    app_strings.dart

    Define constants used throughout the project:

    class AppStrings {
      static const String ENCRYPTION_KEY = "ENCRYPTION_KEY";
      static const String DEVICE_ID = "DEVICE_ID";
      static const String YOU_VERIFY_API_KEY = "YOU_VERIFY_API_KEY";
      static const String GEMINI_API_KEY = "GEMINI_API_KEY";
    }
    

    remote_config.dart

    Handles secure retrieval and storage of API keys using AES encryption. This is a big one, so I’ll break down each part after the code block:

    import 'dart:io';
    import 'package:device_info_plus/device_info_plus.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    import 'package:firebase_remote_config/firebase_remote_config.dart';
    import '../constants/app_strings.dart';
    import '../../../domain/models/custom_error/custom_error.dart';
    import 'package:encrypt/encrypt.dart' as encrypt;
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    
    class RemoteConfig {
      static final FlutterSecureStorage _storage = FlutterSecureStorage();
      static encrypt.Encrypter? _encrypter;
    
      // Initialize AES encryption
      static Future<void> initializeEncrypter() async {
        encrypt.Key key = await _generateEncryptionKey();
        _encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
      }
    
      static encrypt.Encrypter getEncrypter() {
        if (_encrypter == null) {
          initializeEncrypter();
        }
        return _encrypter!;
      }
    
      // Generate a secure encryption key using env variable and device ID
      static Future<encrypt.Key> _generateEncryptionKey() async {
        String envKey = dotenv.env[AppStrings.ENCRYPTION_KEY] ?? "default_secure_key";
        String deviceId = await _getDeviceId();
        String combinedKey = (envKey + deviceId).substring(0, 32);
        return encrypt.Key.fromUtf8(combinedKey);
      }
    
      // Fetch device ID and store it securely
      static Future<String> _getDeviceId() async {
        String? storedDeviceId = await _storage.read(key: AppStrings.DEVICE_ID);
    
        if (storedDeviceId != null) {
          return storedDeviceId;
        }
    
        DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
        String deviceId;
    
        if (Platform.isAndroid) {
          AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
          deviceId = androidInfo.id;
        } else if (Platform.isIOS) {
          IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
          deviceId = iosInfo.identifierForVendor ?? "fallbackDeviceId";
        } else {
          deviceId = "fallbackDeviceId";
        }
    
        await _storage.write(key: AppStrings.DEVICE_ID, value: deviceId);
        return deviceId;
      }
    
      // Fetch and encrypt API keys
      static Future<void> fetchApiKey({required String apiKeyName}) async {
        String key = '';
        try {
          final remoteConfig = FirebaseRemoteConfig.instance;
          await remoteConfig.setConfigSettings(
            RemoteConfigSettings(
              fetchTimeout: const Duration(seconds: 10),
              minimumFetchInterval: const Duration(seconds: 10),
            ),
          );
          await remoteConfig.fetchAndActivate();
          key = remoteConfig.getString(apiKeyName);
        } catch (e) {
          if (kDebugMode) {
            print(e);
          }
          throw CustomError(
            errorMsg: "ERROR Retrieving $apiKeyName (${e.toString()})",
            code: "configuration_error",
            plugin: "",
          );
        }
    
        final iv = encrypt.IV.fromSecureRandom(16);
        final encryptedKey = _encrypter?.encrypt(key, iv: iv).base64;
    
        await _storage.write(key: apiKeyName, value: encryptedKey);
        await _storage.write(key: "${apiKeyName}_iv", value: iv.base64);
      }
    
      static final Map<String, String> _decryptedKeysCache = {};
    
      // Retrieve and decrypt stored API keys
      static Future<String?> getApiKey({required String key}) async {
        if (_decryptedKeysCache.containsKey(key)) {
          return _decryptedKeysCache[key];
        }
    
        try {
          final encryptedKey = await _storage.read(key: key);
          final ivString = await _storage.read(key: "${key}_iv");
    
          if (encryptedKey != null && ivString != null) {
            final iv = encrypt.IV.fromBase64(ivString);
            final encrypted = encrypt.Encrypted.fromBase64(encryptedKey);
            final decryptedKey = _encrypter?.decrypt(encrypted, iv: iv);
    
            _decryptedKeysCache[key] = decryptedKey!;
            return decryptedKey;
          }
        } catch (e) {
          throw CustomError(
            errorMsg: "ERROR Retrieving $key (${e.toString()})",
            code: "configuration_error",
            plugin: "",
          );
        }
    
        return null;
      }
    }
    

    This RemoteConfig class securely fetches, encrypts, stores, and retrieves sensitive API keys using Firebase Remote Config, AES encryption, secure storage, and device-specific information.

    Here’s a breakdown of what’s going on:

    1. Dependencies and Imports

    • dart:io: For platform-specific checks (Android, iOS).

    • device_info_plus: To get a unique device identifier.

    • flutter_secure_storage: For secure local key-value storage.

    • firebase_remote_config: To fetch API keys or configurations from Firebase.

    • encrypt: For AES encryption and decryption.

    • flutter_dotenv: To read environment variables.

    • CustomError: A custom error model used for error handling.

    • AppStrings: Presumably holds constant strings like keys.

    2. Class Properties

    static final FlutterSecureStorage _storage = FlutterSecureStorage();
    static encrypt.Encrypter? _encrypter;
    
    • _storage: For securely storing encrypted keys and IVs.

    • _encrypter: Used to encrypt and decrypt data using AES.

    3. initializeEncrypter()

    static Future<void> initializeEncrypter() async
    
    • Sets up the AES encryptor using a combination of a .env key and the device ID to generate a 32-byte key.

    • Uses AES CBC mode.

    4. getEncrypter()

    static encrypt.Encrypter getEncrypter()
    
    • Returns the existing encryptor or calls initializeEncrypter() if it’s not yet initialized.

    5. _generateEncryptionKey()

    static Future<encrypt.Key> _generateEncryptionKey()
    
    • Combines an environment variable (ENCRYPTION_KEY) and the device ID to produce a 32-character key.

    • Returns an AES key (encrypt.Key.fromUtf8).

    6. _getDeviceId()

    static Future<String> _getDeviceId()
    
    • Checks if a device ID is already securely stored. If not, gets it from the device (Android: androidInfo.id, iOS: identifierForVendor).

    • Stores the device ID securely for future use.

    7. fetchApiKey()

    static Future<void> fetchApiKey({required String apiKeyName})
    
    • Fetches the specified API key from Firebase Remote Config.

    • Encrypts the key using AES and a random IV (initialization vector).

    • Stores both the encrypted key and the IV securely.

    8. getApiKey()

    static Future<String?> getApiKey({required String key})
    
    • Retrieves and decrypts the encrypted API key.

    • If already decrypted and cached in memory, returns it immediately.

    • Otherwise:

      • Reads the encrypted key and IV from secure storage.

      • Decrypts the key and returns it.

      • Caches the decrypted result in _decryptedKeysCache.

    9. Error Handling

    Custom CustomError exceptions are thrown if Firebase fetch or decryption fails.

    This class is built to:

    • Securely fetch API keys from Firebase.

    • Encrypt them using a key tied to both an environment variable and the specific device.

    • Store them locally in an encrypted form.

    • Allow retrieval and decryption with in-memory caching to minimize processing overhead.

    Step 3: Global Initialization

    global_config.dart

    Handles Firebase initialization, dependency injection, and API key retrieval:

    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter/widgets.dart';
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    import 'package:injectable/injectable.dart';
    import 'remote_config.dart';
    import 'app_strings.dart';
    
    class GlobalConfig {
      static Future<void> fetchRequiredApiKeys() async {
        final apiKeys = [
          AppStrings.YOU_VERIFY_API_KEY,
          AppStrings.GEMINI_API_KEY,
        ];
        for (final keyName in apiKeys) {
          await RemoteConfig.fetchApiKey(apiKeyName: keyName);
        }
      }
    
      static Future<void> initConfig() async {
        WidgetsFlutterBinding.ensureInitialized();
        await Firebase.initializeApp();
        await dotenv.load(fileName: ".env");
        await RemoteConfig.initializeEncrypter();
        await fetchRequiredApiKeys();
      }
    }
    

    Step 4: Utilizing the API Key in UI

    main.dart

    Initializes the application:

    import 'package:flutter/material.dart';
    import 'global_config.dart';
    
    Future<void> main() async {
      await GlobalConfig.initConfig();
      runApp(MyApp());
    }
    

    Step 5: Fetching API Key in Widget

    String apiKey = "";
    
    @override
    void initState() {
      super.initState();
      fetchAPIKey();
    }
    
    void fetchAPIKey() async {
      try {
        final key = await RemoteConfig.getApiKey(key: AppStrings.GEMINI_API_KEY) ?? "";
        setState(() {
          apiKey = key;
        });
      } catch (e) {
        print("Error fetching API key: $e");
      }
    }
    

    Common Vulnerabilities in Flutter Apps

    1. Hardcoding Secrets

    Storing API keys or secrets in the codebase (even in .env or .dart files) is one of the most dangerous mistakes. Tools like apktool can extract these secrets easily from the compiled binary.

    Avoid this:

    // Do not hardcode keys
    const apiKey = 'YOUR_SECRET_API_KEY';
    

    Hardcoding secrets is unsafe because when the APK is reverse-engineered, anyone can read those values and misuse your APIs.

    Use secure storage instead:

    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    
    final storage = FlutterSecureStorage();
    await storage.write(key: 'api_key', value: 'your_api_key');
    final apiKey = await storage.read(key: 'api_key');
    

    Using flutter_secure_storage stores secrets securely in platform-specific secure storage mechanisms like Android’s Keystore or iOS’s Keychain.

    2. Lack of SSL/TLS Enforcement (MITM Attacks)

    A Man-in-the-Middle (MITM) attack occurs when an attacker intercepts and potentially alters communication between two parties. This is especially dangerous in unsecured HTTP connections, as sensitive information like login credentials and API keys can be stolen or modified.

    How SSL/TLS Secures Code:

    Secure Sockets Layer (SSL) and Transport Layer Security (TLS) are cryptographic protocols that ensure encrypted communication between a client and a server. This prevents MITM attacks by ensuring that the data is encrypted and cannot be read or altered while in transit. The connection is established over HTTPS (which is HTTP over SSL/TLS).

    Code Example to Enforce SSL/TLS:

    import 'package:http/http.dart' as http;
    
    void makeSecureRequest() async {
      final response = await http.get(Uri.parse('https://yourapi.com/endpoint'));
    
      if (response.statusCode == 200) {
        // Handle successful response
      } else {
        // Handle error
      }
    }
    

    In this case, making sure that the URL starts with https:// enforces the use of SSL/TLS for secure communication

    3. Weak Authentication

    Weak authentication methods are those that are easily guessed or bypassed, such as simple passwords, lack of multi-factor authentication, or weak hashing mechanisms.

    Instead, you should use robust authentication methods like Firebase and OAuth.

    • Firebase Authentication provides various authentication methods such as email/password login, Google sign-in, and phone number authentication. It is a secure and easy-to-implement solution.

    • OAuth is a protocol that allows third-party services (like Google or Facebook) to securely authenticate users without exposing their password to your app. OAuth uses tokens for authorization, ensuring that user credentials are not compromised.

    Use Firebase Auth or OAuth2:

    import 'package:firebase_auth/firebase_auth.dart';
    
    final FirebaseAuth _auth = FirebaseAuth.instance;
    UserCredential userCredential = await _auth.signInWithEmailAndPassword(
      email: 'user@example.com',
      password: 'securePassword',
    );
    final token = await userCredential.user?.getIdToken();
    

    Token-based authentication allows the backend to verify the identity of the user securely without relying on session cookies. Firebase Authentication handles token generation, validation, and expiration for you.

    4. Insufficient Authorization Checks

    Authorization checks are necessary to ensure that the authenticated user has the required permissions to perform certain actions. For example, an admin user may have access to all endpoints, while a regular user may only have access to limited resources.

    How to Verify User Roles/Permissions:

    On the server side, roles and permissions are typically stored in a database. When a user makes a request, the server checks their role and compares it against the required permissions for the requested resource.

    Code Example:

    // Assuming user roles are stored in Firestore
    Future<bool> checkUserRole(String userId, String requiredRole) async {
      final userDoc = await FirebaseFirestore.instance.collection('users').doc(userId).get();
      final userRole = userDoc.data()?['role'];
    
      if (userRole == requiredRole) {
        return true;
      } else {
        throw CustomError(errorMsg: 'User does not have the required role');
      }
    }
    

    Authorization ensures the user is not only authenticated but also has the rights to perform specific actions.

    5. Exposed Endpoints and Metadata

    Exposing Swagger documentation or test endpoints in production can allow attackers to easily discover vulnerabilities in your API. It provides them with detailed information about the structure and capabilities of your API, which can be exploited.

    How to Secure with Route Guards:

    A route guard can prevent unauthorized access to sensitive routes, ensuring that only authenticated and authorized users can access certain endpoints.

    void checkRouteAccess(String route) {
      if (!isUserAuthenticated()) {
        throw CustomError(errorMsg: 'User not authorized');
      }
    }
    

    Avoid this:

    • Don’t deploy Swagger docs without authentication

    • Use route guards for admin/dev routes

    • Strip debug symbols and logs in production builds

    Example: Secure API Call in Flutter

    Here’s a simple example using Dio, a powerful HTTP client for Dart, to securely call an API with token-based authentication and HTTPS:

    import 'package:dio/dio.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    
    final dio = Dio();
    final storage = FlutterSecureStorage();
    
    Future<void> fetchSecureData() async {
      final token = await storage.read(key: 'auth_token');
    
      dio.options.headers['Authorization'] = 'Bearer $token';
    
      try {
        final response = await dio.get('https://yourapi.com/secure-endpoint');
        print(response.data);
      } catch (e) {
        print('API call failed: $e');
      }
    }
    

    This example illustrates how to include an authorization token in your request header and securely make an HTTPS request using dio. Dio also supports interceptors, retries, and advanced options like certificate pinning.

    Best Practices for Securing APIs in Flutter Apps

    Always Use HTTPS

    Avoid plain HTTP at all costs. Use HTTPS to encrypt data in transit.

    final response = await http.get(Uri.parse('https://api.secure.com/data'));
    

    Implement OAuth2 or Firebase Auth

    Use modern authentication packages like firebase_auth or oauth2_client. These offer secure, token-based authentication with built-in session and refresh token management.

    Use Firebase App Check

    Prevents abuse of your backend by verifying the legitimacy of the client app.

    await FirebaseAppCheck.instance.activate(
      webRecaptchaSiteKey: 'your-site-key',
    );
    

    Secure Storage of Sensitive Data

    Use flutter_secure_storage to safely store tokens and secrets locally.

    Obfuscate Dart Code

    Obfuscation makes your Dart code harder to reverse-engineer. You can do this by renaming classes, methods, and variables into meaningless names.

    flutter build apk --obfuscate --split-debug-info=build/symbols
    

    This command strips debug information and renames classes/functions, making it harder for attackers to understand your compiled code.

    Use Rate Limiting and Throttling

    Protect backend APIs from abuse by rate-limiting requests. Implement server-side rate-limiting using API Gateway tools or middleware libraries. Here’s a tutorial that’ll teach you more about this technique.

    Set Up Logging and Monitoring

    Use tools like Firebase Crashlytics or Sentry to track errors and suspicious activity.

    FirebaseCrashlytics.instance.recordError(e, stackTrace);
    

    API Gateway and WAF

    Use API management layers like Google Cloud Endpoints or AWS API Gateway along with Web Application Firewalls (WAF) to control and filter traffic.

    Security Checklist for Flutter Developers

    • Use HTTPS for all communications

    • Never hardcode secrets or credentials

    • Use token-based authentication (OAuth2, Firebase Auth)

    • Validate tokens on both client and server

    • Obfuscate and minify code before production

    • Securely store sensitive data using flutter_secure_storage

    • Enable Firebase App Check or equivalent

    • Use API Gateways and WAFs for traffic filtering

    • Monitor usage logs and set up alerts for anomalies

    • Implement rate limiting to prevent abuse

    Additional Considerations

    Certificate Pinning:

    Certificate pinning is a technique used to ensure that the app only communicates with a trusted server by comparing the server’s certificate against a pre-stored certificate or public key. This prevents attackers from using fraudulent certificates.

    Example: Certificate Pinning in Dio

    class CertPinningInterceptor extends Interceptor {
      @override
      void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
        final context = SecurityContext(withTrustedRoots: false);
        final certBytes = (await rootBundle.load('assets/certs/myserver.cer')).buffer.asUint8List();
        context.setTrustedCertificatesBytes(certBytes);
    
        final client = HttpClient(context: context);
        client.badCertificateCallback = (X509Certificate cert, String host, int port) {
          final serverSha = sha256.convert(cert.der).toString();
          const expectedSha = 'your_cert_sha256_fingerprint';
          return serverSha == expectedSha;
        };
    
        (options.extra['httpClientAdapter'] as DefaultHttpClientAdapter?)
            ?.onHttpClientCreate = (_) => client;
    
        handler.next(options);
      }
    }
    
    • SecurityContext(withTrustedRoots: false): Starts with an empty trust store, meaning no system CAs are trusted by default.

    • setTrustedCertificatesBytes: Loads your own server’s certificate from local assets and sets it as the only trusted certificate.

    • HttpClient.badCertificateCallback: Compares the server’s certificate SHA-256 fingerprint against a known good value. If they match, the request proceeds.

    • onHttpClientCreate: Replaces the default Dio HTTP client with the custom client configured for pinning.

    This ensures that your app will only accept HTTPS connections from your own trusted server, protecting users from certificate spoofing or MITM attacks.

    TTL and Token Rotation:

    Time-to-Live (TTL) is a security measure that ensures tokens automatically expire after a defined period. This limits the duration a token can be used, reducing the attack surface if it’s compromised.

    Token Rotation enhances security further by issuing a new refresh token every time the existing one is used to request a new access token. The previous refresh token is then invalidated. This prevents replay attacks where an attacker might attempt to reuse a stolen refresh token.

    Real-World Token Lifecycle:

    1. Access Token:

      • TTL: Short (for example, 15 minutes)

      • Purpose: Used to authenticate and authorize API requests

      • Behavior: Expires quickly to minimize risk if exposed

    2. Refresh Token:

      • TTL: Longer (for example, 7 days)

      • Purpose: Used to request new access tokens without requiring the user to log in again

      • Rotation: A new refresh token is issued with each use

    Here’s an example Implementation (Dart-like Pseudo-code):

    Generate an access token (15-minute TTL):

    String generateAccessToken(String userId) {
      final expiry = DateTime.now().add(Duration(minutes: 15));
      return createJwtToken(userId: userId, expiresAt: expiry);
    }
    

    Then generate a refresh token (7-day TTL):

    String generateRefreshToken(String userId) {
      final expiry = DateTime.now().add(Duration(days: 7));
      return createSecureRandomToken(userId: userId, expiresAt: expiry);
    }
    

    Refresh the endpoint with rotation:

    Map<String, String> refreshAccessToken(String refreshToken) {
      if (isValidRefreshToken(refreshToken)) {
        final userId = getUserIdFromRefreshToken(refreshToken);
    
        // Invalidate old refresh token
        invalidateRefreshToken(refreshToken);
    
        // Rotate tokens
        final newRefreshToken = generateRefreshToken(userId);
        final newAccessToken = generateAccessToken(userId);
    
        return {
          'accessToken': newAccessToken,
          'refreshToken': newRefreshToken,
        };
      } else {
        throw CustomError(errorMsg: 'Invalid or expired refresh token');
      }
    }
    

    Why this matters:

    • Mitigates long-term exposure: Tokens automatically expire, reducing risk.

    • Prevents replay attacks: A rotated refresh token cannot be reused if intercepted.

    • Enhances session security: Even if a token is stolen, it becomes useless quickly.

    Backend Validation:

    Backend validation ensures that sensitive data, like API keys or JWT tokens, is checked on the server side, preventing tampering from malicious users.

    Never trust the client. Always re-validate all sensitive operations and user roles on the backend.

    Example:

    void validateToken(String token) {
      if (isTokenExpired(token)) {
        throw CustomError(errorMsg: 'Token expired');
      }
    }
    
    • validateToken(String token): A function that takes a user’s token as input.

    • isTokenExpired(token): A hypothetical function that checks whether the token has expired (e.g., by decoding the token and checking its expiry timestamp).

    • throw CustomError(...): If the token is expired, an error is thrown — in this case, a CustomError with a message saying 'Token expired'.

    Why this matters:

    • Tokens can be stolen or manipulated on the client side, so trusting them blindly is dangerous.

    • Backend checks like this help enforce server-side control over user authentication and session validity.

    • Even if a user tampers with client-side code, they can’t bypass this server-side validation.

    Use Security-focused Tools like OWASP ZAP/Burp Suite/Postman:

    Use tools like OWASP ZAP, Burp Suite, and Postman to manually and automatically test your API endpoints for vulnerabilities.

    • OWASP ZAP: Used for penetration testing, finding vulnerabilities like XSS, SQL Injection, and so on.

    • Burp Suite: Another tool for testing security vulnerabilities in web apps.

    • Postman: Can be used for testing API endpoints and ensuring secure communications by adding necessary headers like Authorization.

    Conclusion

    Securing mobile APIs is a foundational requirement in modern app development. For Flutter developers, this means going beyond building beautiful UIs to ensuring the underlying API infrastructure is resilient against threats. The risks of exposed endpoints, leaked secrets, and insecure communication are very real, but preventable.

    Security is about proactive defense, and you should make it a core part of your development workflow. With consistent practices, regular audits, and attention to detail, you’ll protect both your users and your product from unnecessary risks. Flutter provides the flexibility and power to build fast, cross-platform apps – so don’t let poor API security undermine that potential.

    By following the best practices outlined in this article, such as using HTTPS, implementing proper authentication and authorization, securely storing credentials, and leveraging tools like Firebase App Check, you can significantly reduce your app’s attack surface.

    Remember: effective security starts with a mindset. It’s not just a one-time setup, but an ongoing process of vigilance, testing, and improvement.

    References

    • OWASP Mobile Security Project

    • OWASP API Security Top 10

    • Android Security Best Practices

    • Flutter Secure Storage – pub.dev

    • Encrypt Package – pub.dev

    • Firebase Remote Config – Firebase Docs

    • Device Info Plus – pub.dev

    • Flutter dotenv – pub.dev

    • Injectable for Dependency Injection – pub.dev

    • Flutter Fire (Firebase Initialization) – Firebase Docs

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleOpera’s Android browser just got a major tab management upgrade
    Next Article How to Create Documentation with docs.page – A Beginner’s Tutorial

    Related Posts

    Development

    GPT-5 is Coming: Revolutionizing Software Testing

    July 22, 2025
    Development

    Win the Accessibility Game: Combining AI with Human Judgment

    July 22, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    CVE-2025-5579 – PHPGurukul Dairy Farm Shop Management System SQL Injection Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-5026 – Apache HTTP Server Cross-Site Request Forgery (CSRF)

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2024-6107 – Due to insufficient verification, an attacker coul

    Common Vulnerabilities and Exposures (CVEs)

    Update Google Chrome to fix actively exploited zero-day (CVE-2025-6558)

    Security

    Highlights

    Linux

    Rilasciata KDE Gear 25.04: La Collezione di Applicazioni KDE si Rinnova con Tante Novità

    April 17, 2025

    KDE Gear è una collezione di applicazioni sviluppate dal progetto KDE, una comunità attiva che…

    CVE-2025-26855 – Joomla Articles Calendar SQL Injection

    July 18, 2025

    Microsoft won’t be left exposed if something “catastrophic” happens to OpenAI — but may still be 3 to 6 months behind ChatGPT

    May 19, 2025

    CVE-2020-36849 – WordPress AIT CSV Import/Export Plugin Arbitrary File Upload Vulnerability

    July 12, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.