A question I get quite often during my AL development workshops or coaching sessions for partners is this: can other apps access data that is stored in my app?
And the short answer is: yes, they can. There is no way to avoid that.
Let’s not go into the discussion why partners want to prevent others from peeking into data stored in their tables and or to see there table structure. Every partner has their reasons for that and it’s an endless debate between pro’s and con’s.
But let’s be clear about one thing: the data in the database belongs to the customer, not to the partner. It’s their data and nobody should prevent the customer from getting access to their data. Well… that’s my opinion at least… Anyway, there might be situations where you still want to protect data. Let’s look at a few examples.
Storing license data
AppSource doesn’t provide any way to license or monetize your app. So partners are left with two options: either give away their app for free (not an option many would say…) or to build in a license model. Which means they have to store license data somewhere. I think I don’t have to explain potential problems when customers can get their hands on that data and tamper with it. So the question is very valid here: how to store data out of sight of anybody else.
Storing access keys
Another example is when your app has an integration with external servers, e.g. a web service. You don’t want to store any access keys or passwords in tables that can easily be accessed by others.
These two examples can be easily be extended with others. But where it comes down to is the question: how to store sensitive and confidential information.
This is where the new feature of Business Central comes in: Isolated Storage. This feature is briefly described here.
Let’s first make clear what Isolated Storage is not: it’s not a property on a table that indicates that a table is only to be accessed by the app that creates the table. It is not even close to a table at all.
So, what is Isolated Storage? It provides a way to store key / value pairs in the database that can only be accessed by the app that stores that data. The value is simply any text value, not limited to 250 characters. It is stored as a BLOB in a new non-company table called ‘Isolated Storage’.
Data in the Isolated Storage has a scope. The scope determines who has access to the data. By default the data is accessible to all users who run the app. But you can also limit it to a particular user or to a single company. The access level is set with a new datatype, called DataScope.
When you store a value into the Isolated Storage, then you have to indicate the scope. The scope determines who will be able to read the data back from the Isolated Storage.
- Module – Globally in the whole app, across all companies and users
- Company – By all users but limited to the company that is used to store the data
- CompanyAndUser – Only in the company and only by the user who stored the data
- User – Only by the user who stored the data, but across all companies
Isolated Storage API
Now that we have seen that we can actually store data with different access levels, we need to look at how to store and read the data.
There is a new API available: IsolatedStorage. This API has functions to store, read, delete and check data in the Isolated Storage.
I guess the functions speak for itself…
All functions need a key to identify the value you want to store or get. This key is a text and can be anything you choose. The maximum lenght of the key is 200 characters. You can use a GUID or just a readable key or anything that makes sense to you.
All function accept the DataScope as an optional parameter. If you omit DataScope, then the value DataScope::Module will be used.
The data you store is a text value. This could be a simple string or a more complex piece of text like XML or JSON. It is highly recommended to encrypt the value before storing it, which will add another level of security.
Here is an example of how to use IsolatedStorage. This example exports the records of a table called License to JSON, then encrypts the Json value and finally stores the encrypted text in the Isolated Storage.
The function GetStorageKey just returns a GUID:
And with this function the value is read back into the table.
Do not create a codeunit with public functions that expose the sensitive data you store in the Isolated Storage. If you do that, then the Isolated Storage becomes useless, because one could easily create a dependent app on your app, call that function and get his hands on the sensitive data. Only data should be exposed that is safe to be exposed. So think twice about your code that handles the Isolated Storage.
In examples above, the table is declared as a temporary table in a Codeunit. The data never leaves the Codeunit and the Codeunit has only functions to tell if a valid license exists. With that approach, the data is completely isolated and nobody could tamper with it.
I would be even more happy if it was possible to have really isolated tables instead of only storing text values. However, I do understand that it would require a way more complex modification to the platform because it affects features like RecordRef or the option to run a table in the web client. And since isolated storage will probably only be used for specific circumstances, like storing licenses or other confidential data I can certainly see this feature as a solution that fit our needs.
It would be nice to get a generic function to export and import table to JSON though… Now I had to create a function myself to read and write every single field to a JSON value.
Enjoy this new feature and don’t forget to tell Microsoft if you have any improvement for this feature!
Is table 1236 JSON Buffer not available in the Cloud?
It has functions to read and write JSON like the Excel Buffer table has for Excel.
You are right, they *could* be used. But I’m more in favor of the built-in JSON and XML types in AL. Can’t help it…
Also a new table 1235 for xml buffer as well – does that work in the cloud? Plus 1234 for CSV Buffer.
All use dotnet.
Thanks for sharing 🙂
When I remember it correctly than it is forbidden to use the Codeunit EncryptionManagement directly when publishing apps to AppSource. In that case the way leads to Table “Service Password” which should be used for encrypting sensitive data.
The functions Encrypt and Decrypt and others I’m using from the Codeunit EncryptionManagement are all marked as External. Which means they are explicitly made available to be used by Apps from AppSource.
Also, in my app.json I have set target to “Extension”, meaning that I want to limit function calls to what is available for apps on AppSource. It doesn’t show me any warning about using functions from EncryptionMangement.
Table Service Password is an alternative for storing sensitive data from the user, like passwords are access keys that are entered by the user himself. But keep in mind that any app can read that data. In case it would be a problem if certain data gets exposed, I would certainly recommend to store data in Isolated Storage instead of Service Password table.
Yes, you’re right. I remembered it from the AppSource guidlines for v1 Extensions where the complete access to this encryption codeunit was prohibited. It’s fine that the call to the Encrypt and Decrypt function is no allowed.
ISOLATEDSTORAGE.SET(‘myKey’,’MyValue’,DATASCOPE::User); // and any other datascope
Microsoft Dynamics 365 Business Central
The following C/AL functions are limited during write transactions because one or more tables will be locked. Form.RunModal is not allowed in write transactions. Codeunit.Run is allowed in write transactions only if the return value is not used. For example, ‘OK := Codeunit.Run()’ is not allowed. Report.RunModal is allowed in write transactions only if ‘RequestForm = FALSE’. For example, ‘Report.RunModal(…,FALSE)’ is allowed. XmlPort.RunModal is allowed in write transactions only if ‘RequestForm = FALSE’. For example, ‘XmlPort.RunModal(…,FALSE)’ is allowed. Use the COMMIT function to save the changes before this call, or structure the code differently.
It stores values in ordinary Isolated Storage table. So when you set value to isolatedstorage new transaction begins and you get standard “runmodal…” error.
Isolated storage is just another table, like you commented on the GitHub issue. It follows the same rules as other tables, it’s not isolated transaction.
The isolated storage is to save data that can only be read with your app or a more narrow scope like only within a company or even a specific user. That has nothing to do with transactions. If you want to run a modal page after you have stored a value in the database, then your only option is to use commit. Or redesign your code.
But I can create my own table for it. Why should I use ISOLATEDSTORAGE?
That’s totally your own decision.
I use isolated storage in case I want to store confidential information. Sure, you can use your own table for that, but then you have to implement your own security and encryption mechanism. The isolated storage comes with some extra features out-of-the box that let you do more then you can with standard tables (without writing extra code yourself).
I’m not able to retrieve (or check if exist) certificate value that is stored in Isolated Storage from standard certificate page from my extension, because of data scope that is limit on app.
Is there any way to retrieve certificate from my extension?
I don’t think so, it’s what isolated storage is about: exclusive access for your app only.
But then how can I use standard Certificates feature, if Isolated Storage limit usage to only Base app?
Not sure what you want to do with the standard certificates and how it is related to Isolated Storage.
On the SaaS platform, encryption is enabled by default. So you can use IsolatedStorage.SetEncrypted. But this has nothing to do with standard certificates features. It is about encryption, which is explained in detail here: https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-encrypting-data
I thought that I could upload certificate, from certificate page, and use that certificate when I need to contact service. But it looks like that is not possible because of IsolatedStorage datascope limit.
Certificate that is upload from standard certificate page is stored with Isolated Storage.
Ok, now I see. Yes, it seems you can’t use it from an app. All functions do have scope ‘OnPrem’, so that doesn’t help either.
This issue on GitHub does have some additional info: https://github.com/microsoft/ALAppExtensions/issues/2502.
But it doesn’t really provide a solution, I’m afraid.
Yes, we used x509 dotnet for onprem, but for cloud target I didn’t find any smart solution. Thanks anyway for effort to help.
HI AJK 🙂
did you (or anybody other) know how to delete the isolated storage of another user?
I want to save something diffrent in the Isolated Storage with the same key for every user.
But how to force a deletion in their Isolated storage?
I don’t think you can do so from another session. Isolated storage with isolation level user is only accessible from the user session, not from others.
Only option would be to fix when the user logs in.