[page lifecycle series] behaviour of view model instance set to [Protect(ProtectMode.SignData)]

At PostBack we have this:
image

If on the client-side I set a sub view model (that corresponds to a property with no Bind or Protect decorations) to null at the right time, in the Load() handler on the server, that SVM will be null.
Later in the Load() handler: if it is instantiated, then it will be available on the comeback to the client. If not instantiated, it will be null on the client at comeback.

:question: 1: But if instead the property of the SVM is decorated with [Protect(ProtectMode.SignData)], what will the behaviour be in the scenario and cases above?

:question: 2: Is there a way to programmatically on server-side change the Bind or the Protect of a property that holds the SVM instance?

1 Like

1: The ProtectMode.SignData is named slightly confusingly for historical reasons, it actually duplicates the value into $encryptedValues and into the view model JSON. The client-side value then isn’t even sent to the server, DotVVM only reads the “old value” from $encryptedValues. It dosn’t matter if the client sets it to null or changes the value in any way.

Only exception to the rule is if the parent view model is set to null, then the entire model with the encrypted property doesn’t exist, so it will never be deserialized.

2: I’m not sure what exactly are you asking.

Is there a way to set it dynamically - i.e. each user gets different Bind/Protect setting?
No. Workaround is to have 2 properties, e.g. int? UserIdSigned and int? UserIdChangeable. Always make sure that you are reading the Signed first, the UserIdChangeable is also changeable by users which don’t have the rights.

Is there a way to set it during application startup without using the attributes?
Yes. During startup when you have DotvvmConfiguration config (in DotvvmStartup), you can use

config.GetSerializationMapper()
    .Map(typeof(MyViewModel), map => {
         map.Property(nameof(MyViewModel.Property1)).Bind(Direction.ServerToClient);
         map.Property("Property2").Protect(ProtectMode.EncryptData);
    });

I think we never really documented this API. It is stable and we don’t plan to remove it, we just think it isn’t that useful and might easily lead into a convention hell. In short, there is a reason why C# does not have a way to declare properties public/private based on some custom logic.

Hypothetically: First, it could seem like a good idea to automatically Protect(EncryptData) all properties which name ends with “Password”.
…A year later, your new colleague is adding 2FA support to the application, finds the class with Password property, duplicates it and renames to TotpKey. He also notices that storing passwords might not be a good idea, migrates to hashes and renames the Password property PasswordHash. Now you are leaking both of them