Sitemap

Hardening Flutter API Security: Combining JWT, Dynamic TOTP, End-to-End Payload Encryption, and Secure Storage

4 min readAug 20, 2026

--

Press enter or click to view image in full size
Combining JWT, Dynamic TOTP, End-to-End Payload Encryption, and Secure Storage

In high-assurance mobile environments — like fintech, healthcare, and enterprise tooling — a standard Bearer <JWT_TOKEN> is no longer enough. If an attacker intercepts a compromised bearer token or if a rogue proxy captures in-flight traffic, your backend endpoints are exposed.

Get Ebrahim Joy’s stories in your inbox

Join Medium for free to get updates from this writer.

To achieve robust defense-in-depth, sensitive mobile APIs require multi-layered authentication:

  1. Identity & Session Layer: JWT Bearer Token in headers.
  2. Ephemeral Proof-of-Presence: Dynamic Time-Based One-Time Passwords (TOTP) injected into every request.
  3. Payload Shielding: AES-CBC/GCM payload encryption ensuring data cannot be sniffed or modified in transit.
  4. Secure Key Custody: Hardware-backed mobile key storage (flutter_secure_storage using Android Keystore / iOS Keychain).
        ┌─────────────────────────────────────────────────────────────┐
│ Flutter Client Engine │
│ │
│ 1. Load Base32 Secret & AES Keys from Secure Storage │
│ 2. Compute Real-Time TOTP (SHA-512 / 120s step) │
│ 3. Encrypt Request Body (AES-128 / CBC / PKCS7) │
│ 4. Attach Headers: [Authorization: Bearer <JWT>] │
│ [X-TOTP-Signature: <Ciphertext>] │
└──────────────────────────────┬──────────────────────────────┘
│
│ HTTPS Request
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend Gateway │
│ │
│ 1. Validate JWT signature & claims │
│ 2. Decrypt & Validate TOTP against active UTC time window │
│ 3. Decrypt Body Payload via Shared AES Context │
│ 4. Process Business Logic & Return Encrypted Response │
└─────────────────────────────────────────────────────────────┘

Step 1: Secure Storage Layer for Key Material

Never hardcode shared secrets in your release binaries. Store user-specific Base32 seeds and crypto keys inside hardware-backed storage upon successful user enrollment or login.

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureVaultService {
SecureVaultService._();

static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);

static const _keySecret = 'sec_totp_seed';
static const _keyAesKey = 'sec_aes_key';

static Future<void> persistCredentials({
required String totpSecret,
required String aesKey,
}) async {
await _storage.write(key: _keySecret, value: totpSecret);
await _storage.write(key: _keyAesKey, value: aesKey);
}

static Future<String?> getTotpSecret() => _storage.read(key: _keySecret);
static Future<String?> getAesKey() => _storage.read(key: _keyAesKey);
}

Step 2: Payload & Parameter Encryption Manager

To secure payloads, implement AES-CBC encryption with PKCS7 padding. This ensures data remains encrypted even before it hits the network layer.

import 'package:encrypt/encrypt.dart';

class EncryptionManager {
// Use environment configurations or vault-retrieved keys in production
static const String defaultKey = "YOUR_16_BYTE_KEY";
static const String defaultIV = "YOUR_16_BYTE_IV_";

static bool isEncryptionActive = true;

static final _iv = IV.fromUtf8(defaultIV);
static final _encrypter = Encrypter(
AES(Key.fromUtf8(defaultKey), mode: AESMode.cbc),
);

/// Encrypts plain text or serialized JSON into Base64 ciphertext
static String encryptData({required String plainText}) {
if (plainText.isEmpty) return '';
try {
final encrypted = _encrypter.encrypt(plainText, iv: _iv);
return encrypted.base64;
} catch (_) {
return plainText;
}
}

/// Decrypts incoming Base64 ciphertext back to plain text
static String decryptData({required String cipherText}) {
if (cipherText.isEmpty) return '';
try {
return _encrypter.decrypt(Encrypted.from64(cipherText), iv: _iv);
} catch (_) {
return cipherText;
}
}

static String formatParam(String value, {bool? forceEncrypt}) {
final shouldEncrypt = forceEncrypt ?? isEncryptionActive;
return shouldEncrypt ? encryptData(plainText: value) : value;
}
}

Step 3: High-Precision TOTP Generation Engine

This engine computes a synchronous, time-indexed 6-digit OTP computed over UTC timestamps (using SHA-512 with a 120-second step window).

import 'package:otp/otp.dart';

class TotpConstant {
TotpConstant._();
static const int stepSeconds = 120;
static const int digits = 6;
static const Algorithm algorithm = Algorithm.SHA512;
static const int verificationTolerance = 0;
}

class TotpService {
TotpService._();

static String cleanSecret(String secret) {
return secret.toUpperCase().replaceAll('=', '').trim();
}

/// Computes real-time TOTP using UTC epoch
static String generateCode({required String secret, DateTime? at}) {
final time = at ?? DateTime.now();
return OTP.generateTOTPCodeString(
cleanSecret(secret),
time.toUtc().millisecondsSinceEpoch,
length: TotpConstant.digits,
interval: TotpConstant.stepSeconds,
algorithm: TotpConstant.algorithm,
isGoogle: true,
);
}

/// Generates code and formats it through the EncryptionManager
static String getEncryptedTotpCode({
required String secret,
DateTime? at,
bool? forceEncrypt,
}) {
final rawOtp = generateCode(secret: secret, at: at);
return EncryptionManager.formatParam(rawOtp, forceEncrypt: forceEncrypt);
}
}

Step 4: Dio API Interceptor Orchestration

By encapsulating this logic in a HTTP Interceptor, every outgoing request automatically handles JWT injection, dynamic encrypted TOTP generation, and body encryption without cluttering your repository classes.

import 'package:dio/dio.dart';

class SecureApiInterceptor extends Interceptor {
final String jwtToken;
final String totpSecret;

SecureApiInterceptor({
required this.jwtToken,
required this.totpSecret,
});

@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 1. Attach Standard Bearer JWT
options.headers['Authorization'] = 'Bearer $jwtToken';

// 2. Generate and attach Encrypted TOTP
final secureTotp = TotpService.getEncryptedTotpCode(
secret: totpSecret,
forceEncrypt: true,
);
options.headers['X-Signature-TOTP'] = secureTotp;
options.headers['Content-Type'] = 'application/json';

// 3. Encrypt Request Body if present
if (options.data != null && options.data is String) {
options.data = EncryptionManager.encryptData(plainText: options.data);
}

handler.next(options);
}

@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 4. Transparently decrypt incoming response data
if (response.data != null && response.data is String) {
response.data = EncryptionManager.decryptData(cipherText: response.data);
}
handler.next(response);
}
}

Key Architectural Takeaways

  • Zero-Replay Attack Vector: Even if network packets are captured, an intercepted TOTP expires once its 120-second window lapses.
  • Server-Side Clock Alignment: Ensure the backend syncs via Network Time Protocol (NTP) to prevent drift against the mobile client’s UTC calculations.
  • Separation of Concerns: Application repositories interact with plain Dart models; key retrieval, time indexing, and cryptography remain completely decoupled inside the interceptor layer.

Securing modern mobile applications requires moving past the assumption that HTTPS and static bearer tokens are sufficient on their own. By combining hardware-backed key custody through the device’s secure enclave, ephemeral proof-of-presence via synchronized TOTP signatures, and symmetric payload encryption, you effectively close the window on token replay attacks, payload snooping, and reverse-engineering vulnerabilities.

Implementing this multi-layered pipeline directly within your networking layer keeps your business logic clean, predictable, and resilient. Security is never a single feature — it is the deliberate orchestration of defense-in-depth across the entire client-server lifecycle.

--

--

Ebrahim Joy
Ebrahim Joy

Written by Ebrahim Joy

Software Engineer (6 Years) | iOS | Android | Flutter | Java | Swift | Dart @Mesoft Business Solutions, UAE @https://eebrahimjoy.com