approov_service_flutter_httpclient
Approov enabled HttpClient
3.5.8
2.5K downloads/mo
#384 most downloaded on pub.dev
approov/approov-service-flutter-httpclient
What this package is like to depend on
Last release 2 days ago
21 Aug 2026
Release timing varies
gaps range from 1 weeks to 5 months
Nearly every release is documented
notes for 10 of 10 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
10 releases · first in 2025
6 releases in the last 12 months
see the full history below
Release timeline
10 releases · Mar 2025 to Aug 2026
2026
Releases
latest 10-
3.5.821 Aug 2026Release notes
Open source →Upgrade notes:
await ApproovService.initialize(config)now completes only after the native initialization attempt finishes, and throwsApproovExceptionon failure — previously it returned immediately and swallowed native failures. Wrap it in try/catch if you want to fall back to bypass mode (see README).- Every successful
initialize()/re-initialize now resets the runtime configuration (custom mutator, header overrides, token binding, substitutions, exclusions, message signing, cached pinning certificates). Apply configuration afterawait initialize(...)returns; configuration applied before it is discarded. - Mixed-quickstart apps: the service layer no longer tolerates the native SDK reporting that it was already initialized with a different configuration. Previously that outcome was caught and ignored, so an app in which another Approov quickstart had already initialized the SDK with different parameters kept working; it now surfaces as an
ApproovExceptionfrominitialize()(TESTING_REQUIREMENTS.md§1, "Cross-Service-Layer Different Config Initialization" — a different config must not be silently accepted). Align the configuration strings, or use areinit...comment. setUseApproovStatusIfNoToken(true)no longer allows a request to proceed. It is now purely a backend-visibility control over the token header value. If you relied on it to keep requests flowing onNO_NETWORK/POOR_NETWORK, install a customApproovServiceMutatorthat overrideshandleInterceptorFetchTokenResultfor the statuses you actually want to allow —MITM_DETECTEDshould not be one of them.NO_APPROOV_SERVICEno longer sends an empty or prefix-only token header. Backends that treated the mere presence ofApproov-Tokenas evidence the layer ran must now either enablesetUseApproovStatusIfNoToken(true)(which sendsNO_APPROOV_SERVICEas the value) or stop relying on the empty header.- Message signing failures now proceed unsigned instead of aborting the request, except for a required body digest that cannot be generated and an unsupported/missing signing algorithm, which still abort (
TESTING_REQUIREMENTS.md§5; matchesapproov-service-okhttp). Backends enforcing signatures remain the enforcement point.
- Log every substitution that leaves the placeholder in the request. A skipped substitution sends the placeholder where a credential belongs and raises no exception, so the log is the only evidence the decision was taken (
TESTING_REQUIREMENTS.md§3), and previously there was none: a custom mutator declining was silent, and the empty-secure-string case logged at debug, which is below theWARNINGdefault and therefore invisible in the field.UNKNOWN_KEYand an empty secure string now log at warning; any other status can only be skipped because a custom mutator overrode a policy that would otherwise have failed the request, so it logs at error naming the status. - Secure-string substitution now fails closed on
NO_APPROOV_SERVICE. Both the header and query paths previously skipped the substitution and forwarded the request with the placeholder still in the header, reporting no error, so the backend received the placeholder as the credential and the app never learned anything had gone wrong. TheNO_APPROOV_SERVICEcarve-out inTESTING_REQUIREMENTS.md§3 applies to the Approov token fetch, where proceeding without a token is a coherent degraded state that the backend can act on; there is no equivalent for a secret, because the only alternative to failing is sending a wrong secret rather than an absent one. Both paths now throwApproovException, matchingapproov-service-okhttp,approov-service-urlsessionandapproov-service-nsurlsession. Apps that would rather proceed can override the substitution handlers on a custom mutator. Note the consequence: during an Approov outage, requests carrying a configured substitution header now fail instead of going out with the placeholder. - Fix mixed-case staged signing headers leaking a superseded value onto the wire.
ApproovSigningContextlowercases its header keys, but the client staged them case-sensitively, so a custom factory that touched the same header under two spellings (addHeader('X-Foo', …)thensetHeader('x-foo', …), in either order) staged two entries whose replay no longer reproduced the single entry the signature covered: the request went out with both the current and the superseded value while the signature covered only the current one, and the backend could never verify it. Staging is now keyed by the lowercased name, preserving the first casing seen for the wire header. Reachable only from a customSignatureParametersFactory; no effect on the default factory. Covered by a wire-level test rather than a signing-context assertion, since the context was never the broken half. setProceedOnNetworkFail()is now an obsolete no-op (matchingapproov-service-okhttp). The argument is ignored and no default handler reads it, soNO_NETWORK/POOR_NETWORK/MITM_DETECTEDnow fail closed on every path: the token fetch and header/query substitution all throwApproovNetworkException. The flag was a single global switch across every network-related status, so it could not proceed on "no network" without also proceeding onMITM_DETECTED— continuing after the SDK detected interception, potentially before dynamic pins had been received. Express the policy per status instead by overridingApproovServiceMutator.handleInterceptorFetchTokenResult(or the substitution handlers) and installing it withsetServiceMutator.ApproovTokenFetchResult.proceedOnNetworkFailis deprecated and always false.- Fix
setUseApproovStatusIfNoToken(true)acting as a fail-open escape hatch on network and MITM statuses. The default mutator returnedtrueforNO_NETWORK/POOR_NETWORK/MITM_DETECTEDwhenever the flag was set, so enabling a backend-visibility feature silently allowed requests to continue after the SDK had reported detected interception. The default mutator now throwsApproovNetworkExceptionfor those three statuses unconditionally (TESTING_REQUIREMENTS.md§3, "Default Mutator Behavior": fail-closed for every status exceptSUCCESSandNO_APPROOV_SERVICE). The flag now only controls what is put in the token header, never whether a request proceeds. The status-fallback injection path is unchanged and remains reachable for a custom mutator that deliberately overrideshandleInterceptorFetchTokenResultand returnstrue. This diverges fromapproov-service-okhttp, which still has the same escape hatch — that layer needs the matching fix. - Fix an empty or prefix-only
Approov-Tokenheader being sent onNO_APPROOV_SERVICE. With the status fallback disabled the layer emittedApproov-Token:(orApproov-Token: Bearerwith a prefix configured).TESTING_REQUIREMENTS.md§2 ("Missing Artifacts Fallback") requires empty token and trace values to be omitted rather than sent as empty-valued or prefix-only headers, with status evidence provided bysetUseApproovStatusIfNoToken(true)instead. The header is now omitted entirely when no token is available, andNO_APPROOV_SERVICEjoins the status-fallback allowlist so the status name is injected only when that flag is on. The trace-ID header already omitted itself when empty and is unchanged. This diverges fromapproov-service-okhttp, whosebuildTokenHeaderValuereturnsprefix + tokenand so still emits the empty/prefix-only header for this status — that layer needs the matching fix. - Fix
NO_APPROOV_SERVICEturning an Approov outage into a hard request failure for apps using secure-string substitution. The default mutator continues on this status, so_updateRequestproceeded into the substitution loop, whereNO_APPROOV_SERVICEfell through to thedefault:case and threwApproovException— every request carrying a configuredaddSubstitutionHeader/addSubstitutionQueryParamplaceholder failed whenever the Approov service was unavailable, where the previous release forwarded the request unmodified. Both substitution handlers now returnfalsefor this status, skipping the substitution and leaving the original placeholder in place, so the request is forwarded with only the available artifacts (TESTING_REQUIREMENTS.md§2). This diverges fromapproov-service-okhttp, whose substitution handlers still throw for this status — that layer needs the matching fix. - Fix in-flight requests being failed by a rejected re-initialization.
initialize()published the in-flight attempt as the future every_requireInitialized()caller awaits before that attempt had been resolved, so an app protecting traffic with config A that calledinitialize(configB)— a config-refresh path, for example — failed every concurrent request with the native rejection of config B, even though the layer remained fully functional under config A. The attempt is now published only when Approov protection is not already active; while it is,_requireInitialized()keeps resolving against the still-valid initialization for the whole duration of the attempt (TESTING_REQUIREMENTS.md§1, "Different Config Failure State"). Two cases are deliberately unchanged and still gate traffic on the attempt: a first initialization, so a failed attempt stays observable and_requireInitialized()rethrows the original root-cause error rather than the generic "has not been initialized" message; and the "empty config → valid config" upgrade out of bypass mode, so requests issued duringawait initialize(realConfig)wait for it rather than going out unprotected (TESTING_REQUIREMENTS.md§1, "Empty Then Valid Configuration"). Bypass mode reports itself as initialized, so the distinction is protection being active, not the initialized flag. - Move the post-initialization state reset so it sits immediately before the state commit, with nothing that can throw in between. Previously the reset ran, then the platform method-call handler was installed, then
_isInitialized/_initialConfigwere committed — all inside atrythat rethrows asApproovException, so a failure in that window wiped the runtime configuration while_initialConfigstill named the previous config (TESTING_REQUIREMENTS.md§1, "Service-Layer State Only Updated On Success"). - Fix a misleading log line that reported "fallback token header injected" — printing
nullas the value — on any non-SUCCESSstatus that set a token header key. It now fires only when a fallback value was genuinely injected, and names the header it was written to. - Expose
getInstallMessageSignature(message)publicly, completing the common service-layer interface (TESTING_REQUIREMENTS.md§7). The plumbing already existed privately and in both native plugins; only the public entry point was missing, so callers could not sign with the install key outside the automatic interceptor path. Returns base64 of the raw 64-byte ECDSA r||s form, converted from the platform SDK's DER output, which is what an RFC 9421ecdsa-p256-sha256verifier expects. Guarded like its siblings:ApproovExceptionin bypass mode, or when the platform cannot produce a signature. Verified on device on Android and iOS: 64 bytes decoded, and distinct messages produce distinct signatures. - Report the service-layer version in the SDK user property. Initialization previously sent the bare
approov-service-flutter-httpclient, so attestation records could not distinguish one release from another; it now sendsapproov-service-flutter-httpclient/<version>, matching howapproov-service-okhttpreports.ApproovService.serviceLayerVersionexposes the value, and a unit test fails if it drifts frompubspec.yaml. - Fix message signing running after a token fetch that returned
SUCCESSwith no token. Both signing artifacts come from the token itself: install signing is verified against the public key it carries, and account signing uses itsmskidclaim. Signing headers produced without them cannot be verified by any backend, so the request now proceeds unsigned and logs why (TESTING_REQUIREMENTS.md§2 "Missing Artifacts Fallback"). The token header already required a non-empty token; the signing gate did not. - Fix the iOS certificate-collection session leaving non-server-trust authentication challenges uncompleted. The delegate returned without calling the completion handler, so
URLSessionwaited for a disposition and the certificate fetch stalled until it timed out. Unhandled challenges now useNSURLSessionAuthChallengePerformDefaultHandling, the valid fallback (TESTING_REQUIREMENTS.md§4 "Authentication Challenge Dispositions Must Be Valid"). Pre-existing, unrelated to the SwiftPM work. - Fix an empty secure string overwriting the placeholder it was meant to replace. Both substitution paths only checked for
null, so a defined-then-deleted secure string produced an empty or prefix-only header (Beareron the wire) and rewrote a query parameter tokey=, destroying the placeholder the backend needs to see. Both now require a non-empty value and leave the original in place otherwise, logging that the substitution was skipped — matchingapproov-service-retrofitandTESTING_REQUIREMENTS.md§2 "Missing Artifacts Fallback". Applies to the automatic paths and tosubstituteQueryParam(). - Fix secure-string substitution reaching URLs the SDK does not protect (
TESTING_REQUIREMENTS.md§2, "Unprotected Request Processing"). Two paths were affected: the default mutator returnedtrueforUNPROTECTED_URL, which let header substitution run after the token fetch, and automatic query substitution ran before any token fetch could classify the URL, becausedart:iofixes a request's URI atopenUrl()time. Either could resolve a secure string into a request bound for a host Approov neither tokenizes nor pins. The default mutator now returnsfalseforUNPROTECTED_URL(joiningUNKNOWN_URL, and matchingapproov-service-okhttp), and query substitution now runs only when a pre-open classification positively confirms the URL is protected — any other outcome, including a network failure or an internal error, suppresses it. A custom mutator overridinghandleInterceptorFetchTokenResultcan opt header substitution back in, but not automatic query substitution: that is gated by the pre-open URL classification, which runs before any mutator is consulted and requires a confirmedSUCCESS. Apps that need a query parameter substituted regardless can callsubstituteQueryParam()explicitly. - Document that the config-taking constructors
ApproovHttpClient([config])andApproovClient([config])cannot report an initialization failure to the caller: a constructor cannot await, soinitialize()'s new asynchronous throw is not catchable around construction. The failure is retained and rethrown from the first request through the client, and the pending error no longer surfaces as an unhandled asynchronous error. Preferawait ApproovService.initialize(config)before constructing the client, which is the pattern the README documents. - Add
isInitialized()andisApproovEnabled()public API methods (Dart, Android, iOS). - Fix native initialization state being tracked per
FlutterEnginerather than per process. The plugin instance is created once per engine, so an app with a second engine (background fetch, alarm or geolocation plugins) saw "not initialized" there while the process-wide Approov SDK was initialized and protecting traffic — and the Dart layer read that as "native unprotected" and committed real bypass mode, silently dropping token injection and pinning for every request from that engine. The state is now held in a process-wide static on both platforms (matchingapproov-service-okhttp), andisInitialized()/isApproovEnabled()are answered from it. - Query the native state over the background method channel when the foreground channel cannot answer, so
isInitialized()/isApproovEnabled()and the empty-config protection probe fall back to a second route rather than treating one failed probe as an established state. This is defence in depth, not a fix for an observed defect: every other native call in the package uses the foreground channel unconditionally and works from background isolates. When neither channel answers, bypass is assumed (perTESTING_REQUIREMENTS.md§1) and now logged at error level rather than passed over silently. - Document the bypass-mode contract in
REFERENCE.md: the guarded methods do not behave uniformly (throw / empty map / empty string / no-op / pass-through), and that difference is now stated per method. - Document that the
commentargument participates in the native SDK's already-initialized matching, so a same-config re-initialization is accepted only with an identical comment or one starting withreinit(TESTING_REQUIREMENTS.md§1, "Comment Is Part Of The Platform SDK Initialization Identity"). - Guard the iOS
initialConfigargument againstNSNull, as thecommentandupdateConfigarguments already are:-[NSNull length]is an unrecognised selector and would crash rather than read as empty. - Fix
initialize('')(empty configuration string) to actually enter bypass mode — initializes the service layer without calling the native Approov SDK, instead of throwing. Previously this would fail with a native exception surfaced as a DartPlatformException, contradicting documentation that claimed bypass-mode support. - Fix
initialize()re-initialization guard to allow the "empty config → valid config" upgrade transition and to silently ignore a "valid config → empty config" downgrade attempt, per the cross-service-layerTESTING_REQUIREMENTS.mdspec, instead of throwing in both directions. - Fix initialization to await the current native initialization attempt, preserve
nullcomments when forwarding to native, forward every non-empty config to the native SDK, and preserve existing service-layer state when native initialization fails. - Reset runtime service-layer configuration after every successful initialization/re-initialization, including custom mutators, header overrides, token binding, substitutions, exclusions, message signing, and cached pinning certificates.
- Fix bypass mode (empty configuration string) to genuinely behave as a plain, unprotected network client end-to-end, matching
approov-service-react-native's pattern: the core request pipeline now skips token injection, pinning, and secure string substitution entirely for every request, and every other public method that talks to the native SDK directly (precheck,getDeviceID,fetchToken,getMessageSignature,getAccountMessageSignature,fetchSecureString,fetchCustomJWT,setDevKey,getPins,setDataHashInToken,substituteQueryParam) now fails cleanly withApproovException("Approov is not enabled")(or a safe no-op/empty default, where that is the correct behavior) instead of crashing with a native exception. Previously onlyinitialize('')itself worked; every other entry point still reached the uninitialized native SDK. - Fix a cross-thread race on iOS between the background-channel
initializewrite and the foreground-channelisInitialized/isApproovEnabledreads of the same state, matching the equivalent Android fix (volatilefields). - Fix automatic token binding to await
setDataHashInToken(...)before fetching the bound Approov token. - Fix message signing fallback behavior so signing and serialization failures fail open, while required body-digest failures and unsupported algorithms still fail closed.
- Fix Structured Fields date serialization conformance for syntactic min/max date values.
- Fix a race where overlapping failed re-initialization attempts could leave a healthy, successfully initialized service throwing a stale initialization error from every API call: a failed attempt now restores a freshly resolved initialization future (never a captured earlier one) whenever a successful initialization is in effect.
- Fix a state desynchronization window where a failure in the post-initialization telemetry call (
setUserProperty) failed the wholeinitialize()after the native SDK had already committed — leaving Dart in bypass while native was protected. The Dart state now commits immediately after native success and the telemetry call is best-effort (matchesapproov-service-okhttpordering). - Fix a cross-isolate divergence where a fresh isolate (or hot restart) calling
initialize('')while the process-wide native layer was already protected would commit bypass mode locally — silently skipping pinning and token injection for that isolate's requests whileisApproovEnabled()reportedtrue. The Dart layer now queries native and adopts protected mode. - Fail-closed message signing classification now uses typed exceptions (
RequiredBodyDigestException,UnsupportedSignatureAlgorithmException, both exported) instead of error-message string matching; a params object with a missing algorithm identifier now also fails closed, matchingapproov-service-okhttp. CustomSignatureParametersFactoryimplementations can throwRequiredBodyDigestExceptionto force an abort. - Fix staged message signing header application to preserve multi-value header adds from custom factories (previously collapsed to the last value, producing signatures the server could never verify). Note: as in
approov-service-okhttp, when signing fails open the request goes out withoutContent-Digest(signing-related headers are staged and only applied on success). - Log (rather than silently discard) the native SDK's already-initialized result on a same-config re-initialization, on both platforms.
- Raise the
loggerdependency lower bound to^2.1.0(DateTimeFormatis used and was added in 2.1.0). - Deprecate
prefetch()— it is now a no-op, matching the rest of the Approov service layer family (approov-service-retrofit,approov-service-urlsession, and others). The Approov SDK manages prefetching automatically; the explicit prefetch call is redundant.
-
3.5.613 Mar 2026Release notes
Open source →- Add
ApproovServiceMutatorsupport across fetch APIs, request mutation flow, and pinning gate callbacks. - Add request mutation models:
ApproovRequestMutations,ApproovRequestSnapshot,ApproovTokenFetchResult, andApproovTokenFetchStatus. - Add
setServiceMutator()/getServiceMutator()plus deprecated alias methods for naming parity. - Add service-layer logging controls:
ApproovLogLevelwithOFF,ERROR,WARNING,TRACEandsetLoggingLevel()/getLoggingLevel(). - Add detailed TRACE diagnostics for platform-channel method calls, timing, and failures (with sensitive-value redaction).
- Add automatic query substitution APIs:
addSubstitutionQueryParam()andremoveSubstitutionQueryParam(). - Add
setUseApproovStatusIfNoToken(bool)andgetUseApproovStatusIfNoToken()to control token-header status fallback behavior. - Add interceptor token-header fallback injection for allowlisted statuses when no token is available:
NO_NETWORK,POOR_NETWORK,MITM_DETECTED. - Preserve mutator-first decision ordering: fallback injection only occurs when
handleInterceptorFetchTokenResult(...)allows continuation. - Ensure configured token header name and prefix from
setApproovHeader(...)apply equally to JWT and status fallback values. - Propagate Approov trace IDs to request headers.
- Update
USAGE.mdandREFERENCE.mdwith status-fallback behavior, defaults, allowlist, and mutator interaction. - Restructure docs to OkHttp-style layout with
README.md,USAGE.md, andREFERENCE.md. - (fix) Don't throw exception on missing public key
- Add
-
3.5.518 Dec 2025Release notes
Open source →- Updates Approov IOS SDK to 3.5.3
- Add a capability to retrieve an ARC(Attestation Response Code) via getLastARC()
- Add a capability to retrieve pins from the Approov SDK via getPins().
-
3.5.405 Dec 2025 -
3.5.325 Nov 2025 -
3.5.121 Oct 2025 -
3.5.031 Jul 2025 -
3.4.220 May 2025Release notes
Open source →- Async service initialize function now returns a future to enable awaits
- Fix pub.dev listing to link to the correct github repo
-
3.4.109 May 2025Release notes
Open source →- Support calling Approov from main isolate and any background isolate
- Performance improvements
- Allow reinitialization with the same configuration
- Edge case bug fixes
- Align major and minor version with native SDK
-
0.0.502 Mar 2025Release notes
Open source →- Updated readme.
- First published to pub.dev
- Update iOS native pod package to 3.3.1