Storing Data Securely
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.
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
.
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
});
Apps can save and delete data only for the active session user.