Securing Local Storage for dApps

Deploying a decentralized application introduces some interesting security considerations that might not come up in more traditional development.

One important obstacle we’ve run into while working on the Colony dApp is the challenge of keeping locally stored dApp data secure with a distributed storage system like IPFS or Swarm.

In this article I’m going to take a look at the problem from a dApp developer’s perspective, then examine some possible solutions.

The problem of Shared localStorage

IPFS runs a local ‘node’, which comes bundled with a web server. The bundled web server makes it easy for nodes to connect to each other and share data that might be needed somewhere else in the network.

As a decentralized app builder, you’re going to rely on that web server to push and pull your content from node to node, making it instantly available to an end-user as needed.

Assuming that you’re going ‘full decentralized’ and are avoiding anything like DNS or web proxies to keep track of where your content is on the network, the way to access your dApp would ordinarily be to query the local node through a browser using its hash:

http://localhost:8080/QmcefGgoVLMEPyVKZU48XB91T3zmtpLowbMK6TBM1q4Dw/

Now, let’s say that during normal use your application is going to save data inside your browser’s localStorage — maybe you need to pass some data around, or are keeping a queue of user interactions local to minimize on-chain transactions and save gas costs.

The local storage within your browser is restricted to a specific address context (domain and port). The IPFS node is what gets this context — meaning that any distributed app running through the IPFS web server will be using the same localStorage with read and write access.

This is potentially a big problem.

Some helper dependencies for a dApp by default use localStorage to temporarily keep keys in plaintext.

This data shouldn’t see the light of day

Another potential leak is packages that save their memory state so that it can be restored at a later time. Flux-like libraries ordinarily are (relatively) safe because they run only in memory — but enabling state persistence will put that memory state into localStorage and thus open it up to potential attackers.


Strategies for mitigation

Unfortunately, there’s no magic bullet for security — as a dApp developer any adaptation made for the sake of security is likely going to require some concessions in other aspects of development.

Here are some compromises you can make:

Store no data

This is certainly the safest approach, but it’s a bit like burning your house down to get rid of the cockroaches. There are a lot of features and essential behaviors inside a dApp that store data locally, and you might not have much of an app left after you remove them.

Furthermore, there are a number of libraries out there that use localStorageby default — you’ll have to manually inspect each of your dependencies and remove any that require it, or else modify the libraries yourself.

Encrypt everythingThis is a bit more promising in theory, especially since most dApp developers are already on-board with keeping things encrypted by default.

Encrypted local storage values

In practice encrypting all your local storage is a bit more troublesome. To encrypt data, one must have a key — but a user can’t store that key within the dApp because it’ll be put inside localStorage and you’ll be back to square one.

One solution is to use a wallet: your dApp will likely be interacting with a blockchain in some way, requiring the user to unlock their wallet to send and sign transactions. Since the wallet will be needed to interact with the dApp anyway, it would be possible to use each account’s privatekey to encrypt local storage.

This, however, also comes with some drawbacks:

  • You must ask users for their plaintext private key each time they want to interact with localStorage.
  • Key management software like MetaMask will not work because it never exposes the user’s private key.

Use exclusively Swarm and Mist

Mist is built as both a dApp and Ethereum browser, so it offers some special advantages for the problem.

Mist supports Swarm'sbzz protocol by default, so you could set up an ensaddress to point to your dApp’s hash, and then use Mist to browse your dApp without worry.

Unfortunately, this will only solve the problem for users that access the dApp through Mist.

A user running a local Swarm node will still have to access via localhost,which will still be (potentially) leaking data to other dApps.

Create a browser extension for your dApp

Running your app through a browser extension will result in it getting a separate context (it won’t be on localhost:8080 anymore) — but it kind of defeats the purpose of decentralizing an app only to rely on a central authority like chrome web store for management and distribution.

Also, now you’ll have to create and maintain a separate extension for each browser that you want to support, and update each through its own specific centralized app store. Yuck.

Create a standalone desktop app

As before, creating a standalone app is a way to separate your dApp into its own context, meaning it’d get its own wrapper (in this case, electron).

A standalone desktop app has the added benefit of being able to bundle external libraries and anything else you might need, including a separate instance of IPFS itself.

Also as before, there are some concessions:

  • Unless you want to distribute the app exclusively on bittorrent, you’ll need to find a centralized hosted solution for distribution and maintenance.
  • You’ll have to maintain a separate repository just for the electron desktop app.
  • If you want to use IPFS for any other service, you’ll likely end up running multiple nodes on the same machine, which could get messy.

Proxy your app to a domain name

By using another web server to proxy your local node, there are two advantages:

First, now your dApp has a nice friendly human-readable address, instead of a long cumbersome hash. Second, your app would then have its own context, and wouldn’t share localStorage.

Proxying does, however, cross the line of ‘true’ decentralization — users will again have to rely on a centralized server to access a decentralized service. Boo.


It’s still early days for decentralized app developers. Problems like this are ubiquitous throughout the emerging ‘decentralized protocol stack’ — and it’ll likely be a while before we come up with more elegant solutions.

In the future, support for a native IPFS or Swarm node within a browser would solve this problem— and eliminate the need to bundle a web server with decentralized file storage at all. Users could input something like ipfs://QmcefGgoVLMEPyVKZU48XB91T3zmtpLowbMK6TBM1q4Dw/ and get access to a dApp directly, with each unique hash being assigned its own context.

The Mist and IPFS teams are aware of the issue, and hopefully will be incorporating a solution into future releases.

For now, though, it’s helpful to find what workarounds we can and to share them with the rest of the community.

If you’re a developer working on your own decentralized app, hopefully this article has been helpful to you in structuring your code to avoid data leakage — if you’ve devised another solution that wasn’t mentioned above, share it!

Thanks to Alex Rea and Griffin Hotchkiss for their help in drafting this article.


Raul is a Javascript developer who spends most of his time in a terminal emulator.

For the past decade he’s been working in the tech industry doing sysadmin work, back-end development and even some electronics.

When not writing code at Colony he pursues his passion for flying.


Colony makes it easy for people all over the world to build organisations together, online.

Join the conversation on Discord, follow us on Twitter, sign up for (occasional and awesome) email updates, or if you’re feeling old-skool, drop us an email.