Skip to main content

Storing Data Securely

Connect 🌐

Stacks.js starters offer working templates with Stacks Connect pre-installed for a quick and easy way to get started with building Stacks enabled web apps.

This guide explains how to save and retrieve data for users with Gaia by implementing the connect and storage packages of Stacks.js.

Data storage provides a way for users to save both public and private data off-chain while retaining complete control over it.

Storing data off the Stacks blockchain ensures that apps can provide users with high performance and high availability for data reads and writes without the involvement of centralized parties that could compromise their privacy or accessibility.

See the To-dos app tutorial for a concrete example of this feature in practice.

Install dependencies

The following dependencies must be installed:

npm install @stacks/connect @stacks/storage

Initiate session

Users must authenticate to an app before the storage package will work to save or retrieve data on their behalf.

See the authentication guide before proceeding to integrate the following data storage capabilities in cases where userSession.isUserSignedIn() returns true.

Save data for session user

Gaia serves as a key-value store in which data is saved and retrieved as files to and from Gaia hubs owned by, or managed for, users.

The default Gaia hub for users who authenticate to apps with the Leather Wallet is run by Hiro PBC at https://gaia.blockstack.org/. It supports files up to 25 megabytes in size.

tip

Hiro recommends breaking data instances greater than 25 MB into several files, saving them individually, and recomposing them on retrieval.

These files can comprise any type of data such as text, image, video or binary.

Files are often saved as strings that represent stringified JSON objects and contain a variety of properties for a particular model.

To save a file, first instantiate a storage object using the userSession object for an authenticated user. Then proceed to call its putFile method with relevant parameters:

import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';

const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });

let fileName = 'car.json';

let fileData = {
color: 'blue',
electric: true,
purchaseDate: '2019-04-03',
};

const options = {
encrypt: true,
};

let fileUrl = storage.putFile(fileName, JSON.stringify(fileData), options).then(() => {
// Handle any execution after data has been saved
});

The options parameter object contains an encrypt property that when set to true indicates that the data should be encrypted with the user's app private key before saved to their Gaia hub. All data will be encrypted as such by default if the encrypt property or the options object itself is omitted entirely.

If the encrypt property is set to false, the data will be saved completely unencrypted and available to everyone online with public access to the user's Gaia hub.

Whereas saving privately encrypted data is possible for all authenticated apps with the store_write scope, the user must have previously granted the publish_data scope as well during authentication for the app to save publicly unencrypted data.

The putFile method returns the URL where the the file can be retrieved from the user's Gaia hub, as used here to set the value of fileUrl.

info

You'll need to save an entirely new string of modified data using putFile with the same fileName every time you want to update a record. There is no separate update method.

Get data for session user

To retrieve data previously saved for a user with an app, call the getFile method available from the storage object:

import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';

const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });

let fileName = 'car.json';

const options = {
decrypt: true,
};

storage.getFile(fileName, options).then(fileData => {
// Handle any execution that uses decrypted fileData
});

Note how the decrypt property in the options object here should implement the same boolean value as used for encrypt initially upon saving the data with putFile. The decrypt property will default to true if omitted.

Encrypted files need decrypt set to true so the app knows to decrypt the data with the user's app private key before made available in the callback here as fileData.

Get data for other user

Apps can also retrieve public data saved by users other than the one with the active session, granted those users have registered usernames via the Bitcoin Naming System.

Simply indicate the username of such a user in the options object:

import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';

const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });

let fileName = 'car.json';

const options = {
username: 'markmhendrickson.id.blockstack',
};

storage.getFile(fileName, options).then(fileData => {
// Handle any execution that uses decrypted fileData
});

This getFile call will retrieve data found at the given fileName path from the storage bucket of the Gaia hub that maps to the user who registered the given username and this particular app as hosted at the current domain.

Set an additional app property within options to retrieve data for a user as saved by an app hosted at a separate domain:

const options = {
app: 'https://example.org',
username: 'markmhendrickson.id.blockstack',
};

This will cause the getFile call to retrieve data found in a separate storage bucket for the indicated app on the user's Gaia hub.

Delete data for session user

Call the deleteFile method on storage to remove data found at a particular file path for the active session user:

import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';

const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });

let fileName = 'car.json';

storage.deleteFile(fileName).then(() => {
// Handle any execution after file has been deleted
});
info

Apps can save and delete data only for the active session user.