flutter_secure_storage icon indicating copy to clipboard operation
flutter_secure_storage copied to clipboard

OperationError when calling `read()` on web

Open shroff opened this issue 3 years ago • 18 comments

I'm getting the following error when calling read() on web, only if the key exists in storage. It works fine if the key does not exist. Calling containsKey() returns the correct value and does not produce this error.

Error: OperationError


    at Object.createErrorWithStack (http://localhost:5000/dart_sdk.js:5080:12)
    at Function._throw (http://localhost:5000/dart_sdk.js:20337:18)
    at Function.throwWithStackTrace (http://localhost:5000/dart_sdk.js:20334:18)
    at async._AsyncCallbackEntry.new.callback (http://localhost:5000/dart_sdk.js:40851:18)
    at Object._microtaskLoop (http://localhost:5000/dart_sdk.js:40708:13)
    at _startMicrotaskLoop (http://localhost:5000/dart_sdk.js:40714:13)
    at http://localhost:5000/dart_sdk.js:36191:9

This seems to be triggered from invoking the callback function in the following block:

    static _scheduleImmediateWithPromise(callback) {
      dart.addAsyncCallback();
      dart.global.Promise.resolve(null).then(() => {
        dart.removeAsyncCallback();
        callback();
      });
    }

I'm running Chromium 99.0.4844.82 on Arch Linux

Any thoughts on what this could be caused by?

NB: OperationError seems to be a DomException constant.

shroff avatar Mar 25 '22 08:03 shroff

I'm encountering the same error on macOS.

pedro371 avatar Mar 25 '22 13:03 pedro371

+1

SlavisDEV avatar Mar 29 '22 08:03 SlavisDEV

+1 I also have this on web

duck-dev-go avatar Mar 30 '22 15:03 duck-dev-go

From what I can see the error is in

flutter_secure_storage_web.dart:152

  Future<String?> _decryptValue(
    String? cypherText,
    Map<String, String> options,
  ) async {
    if (cypherText == null) {
      return null;
    }

    final parts = cypherText.split(".");

    final iv = base64Decode(parts[0]);
    final algorithm = _getAlgorithm(iv);

    final decryptionKey = await _getEncryptionKey(algorithm, options);

    final value = base64Decode(parts[1]);

    final decryptedContent = await js_util.promiseToFuture<ByteBuffer>(
      crypto.decrypt(
        _getAlgorithm(iv),
        decryptionKey,
        Uint8List.fromList(value),
      ),
    );

    final plainText = utf8.decode(decryptedContent.asUint8List());

    return plainText;
  }

in this block

   final decryptedContent = await js_util.promiseToFuture<ByteBuffer>(
      crypto.decrypt(
        _getAlgorithm(iv),
        decryptionKey,
        Uint8List.fromList(value),
      ),
    );

Might this be relevant?

https://stackoverflow.com/questions/68160151/how-to-fix-this-operationerror-error-when-decrypting-data-with-crypto-api

Here is the relevant part of the stack trace

image

duck-dev-go avatar Mar 30 '22 18:03 duck-dev-go

I get this error as well. Have any of you found out why, or a fix?

This is not happening on all read()s in my case. I't is one of the keys that constantly fail. I have tried using dummy values and the same values for the two keys, but it is always only one of them that fails.

Also, how do you get a thorough stack trace like that? All I see in my logs when doing catch(e,s) and printing those are the OperationError

momrak avatar Apr 08 '22 07:04 momrak

I used the webserver command

flutter run -d web-server

And then I got a thorough stack trace. I think this is a issue with the way the decrypting is done, and I myself do not have much experience with encryption and know that it is easy to make mistakes if not done properly.

duck-dev-go avatar Apr 08 '22 12:04 duck-dev-go

I had the same issue on macOS using the Web version.

My mistake was not using async / await properly - after adding "await" to ALL read / write functions, everything was working fine.

Hope that helps!

FrankNanninga avatar Apr 09 '22 18:04 FrankNanninga

Thanks for both tips! Managed to get it working by creating a class for my fields and save them as JSON strings. When doing a read on that field then I had no issue 🤷 Will see if I bump into it again

momrak avatar Apr 12 '22 09:04 momrak

@momrak were you storing a JWT by any chance,? We are. I wonder if the decryption fails for certain characters. Thanks for the tip!

duck-dev-go avatar Apr 12 '22 09:04 duck-dev-go

@momrak were you storing a JWT by any chance,? We are. I wonder if the decryption fails for certain characters. Thanks for the tip!

JWTs are Base64url encoded and separated by '.'s, so they should not contain any characters other than [A-Za-z0-9-_.]. It would be very strange if this character set is unsuitable for storage.

shroff avatar Apr 12 '22 11:04 shroff

@duck-dev-go Yes, this was the case for an access token JWT. But I tried changing to simple strings as well just to see if that helped, but I still got then problem when doing that. Hope combining them might work for you as well :)

momrak avatar Apr 12 '22 12:04 momrak

I can confirm that storing it as json works. I wonder why. Maybe you can't store multiple things? Anyway thanks @momrak !

duck-dev-go avatar Apr 15 '22 22:04 duck-dev-go

I think what is happening is that when you want to store multiple values it overrides the encryption key. So that is why it works as json. Because the name of the encryption key will stay the same in localstorage. So I don't think this is a bug, but docs would help.

duck-dev-go avatar Apr 15 '22 22:04 duck-dev-go

Faced this issue too. For some reason, it occurs only when I run the app on localhost.

AndreiMisiukevich avatar May 12 '22 01:05 AndreiMisiukevich

I found a workaround for this issue. Make sure you're not writing to the storage concurrently. Just await for each operation.

Consider having this function.

 Future<void> _writeString(String key, String value) async {
    const storage = FlutterSecureStorage();
    await storage.write(key: key, value: value);
  }

Calling it like this will produce read errors further on

_writeString("key1", "data");
_writeString("key2", "some other data");

but awaiting for them will work just fine

await _writeString("key1", "data");
await _writeString("key2", "some other data");

apetroaeiandrei avatar May 17 '22 09:05 apetroaeiandrei

#427 Hi guys, it looks like you are using the package for the web. Can you please explain some facets to me, I have to implement a web app and times are tight. You would do me a great pleasure if you give me examples and maybe explain the bare minimum. I'm finding it wrong with the documentation because it doesn't exist.

@shroff , @pedro371 , @SlavisDEV , @duck-dev-go , @momrak

PS: Sorry for the tags but I have delivery times, Thanks in advance

paulFlow33 avatar Aug 08 '22 15:08 paulFlow33

thank you apetroaeiandrei!

i added the synchronized package https://pub.dev/packages/synchronized, then wrapped my writes with:

    await _lock.synchronized(() async {
      await secureStorage!.write(key: key, value: value);
    });

operationerror issues gone!

thinkingatoms avatar Aug 14 '22 18:08 thinkingatoms

Encoding object as json works, thx @momrak

Thioby avatar Jul 25 '23 10:07 Thioby