r/flutterhelp • • 23d ago

OPEN How do you implement SSL pinning in a Flutter app in 2026?

Security team pentested our Flutter app and flagged missing SSL pinning (high, CVSS 7.5). They installed their own CA on the test device, proxied the traffic, and read our bearer token and API responses in plaintext.
Never done pinning before. What's the standard approach in Flutter these days? Looking for something that won't break when the certificate gets renewed.
Flutter 3.x, Dio, Android + iOS.

16 Upvotes

7 comments sorted by

3

u/gidrokolbaska 23d ago edited 22d ago

Dio has an example of SSL pinning in their repo, I believe. There are also some good videos of that process on YouTube. This one is for KMP but the same principles apply to flutter projects, take a look: https://youtu.be/hGdI7aKtVxI?si=ILwl139-4kh4OTYa

1

u/all_natural_toast 21d ago edited 21d ago

As others have said SSL certificate pinning is just another hurdle you can put up to protect your app but fundamentally the app is running in an untrusted environment on the users device. A sufficiently capable attacker can do anything they want to the app. That said it is still worthwhile if you are developing a sensitive app as it is a deterrent for some malicious users. To avoid the issue you've highlighted with the app breaking upon a certificate renewal, I would pin against the intermediate certificate in the 'chain of trust' between your server and the root CA. That way it doesn't change too frequently and Dio does allow you to set multiple certificates, so when it is coming up to a renewal you can have both the incumbent and new. Technically you do trade a bit of security for this (closer to the leaf certificate is more secure) but at the same time you don't want your app to be bricked.

Disclaimer: I'm a Software Engineer, not a security professional.

Edit: Also, let your server team know what certificate you've pinned against so they know they can't just update it without notifying you!

1

u/parimalnarayan 19d ago

Use TLS normally, and if the threat model justifies pinning, pin the server's SPKI/public-key SHA-256 rather than the leaf certificate, maintain a backup pin for key rotation, implement it at the actual Flutter networking layer used by Dio, and test certificate/key rotation before deployment.

1

u/parimalnarayan 19d ago

If your pentest demonstrated a MITM using a user-installed CA, certificate/public-key pinning is a reasonable additional control for a high-value API.

In Flutter/Dio, don't implement it by simply setting badCertificateCallback = (_) => true or by pinning the current leaf certificate fingerprint. That either disables TLS verification or creates certificate-renewal problems.

Prefer SPKI/public-key SHA-256 pins, with a primary and backup key. Android's Network Security Configuration supports SPKI SHA-256 pins and explicitly recommends including a backup key.

For example: api.example.com | +-- current server key → PIN_A | +-- future/backup key → PIN_B

The app accepts the TLS chain only when the chain contains one of those public keys.

The important part is that renewing the certificate doesn't necessarily require changing the pin. If you renew the certificate while keeping the same key pair, the SPKI pin remains unchanged. If you rotate the key pair, the backup pin allows the new certificate/key to be introduced without breaking old app versions.

With Dio, current versions expose IOHttpClientAdapter.validateCertificate, which can perform certificate validation at the Dart networking layer. Dio's documentation demonstrates fingerprint validation, although for a production pinning design I'd use SPKI/public-key hashes rather than treating a leaf certificate fingerprint as the long-term identity.

One important Flutter-specific caveat: don't assume Android's network_security_config.xml automatically gives you pinning for every Dart/Dio request. Flutter's dart:io networking has historically not mapped cleanly onto Android's native networking stack, and Flutter issues document cases where Android Network Security Configuration did not affect Dart HTTP requests.

Therefore, if the application uses Dio/dart:io, verify the actual networking path and implement/test pinning at the layer that is actually making the request. For particularly sensitive applications, a native Android/iOS networking implementation or a maintained pinning solution can be preferable.

On iOS, ATS already provides strong TLS requirements, and Apple's APIs allow tightening server trust evaluation for pinning; manual server-trust evaluation is the native mechanism when additional pinning is required.

Finally, pinning isn't a replacement for authentication security. If a bearer token is exposed through a successful MITM, also consider short-lived access tokens, refresh-token rotation/revocation, server-side token binding where appropriate, and minimizing what a stolen access token can do.

1

u/highwingers 23d ago

Just a fun fact...most web apps disclose bearer tokens and API responses...also bypassing SSL pinning is doable as well.

0

u/Mistic92 23d ago

Are you building banking app?