DEV Community

Robert Chen
Robert Chen

Posted on • Originally published at Medium on

7 3

How to use Gaia Storage with Blockstack

Final part of a three-part tutorial

In part two, How to Connect Blockstack to your Backend API, I show you how to create a user object in your API after logging in with Blockstack. In this tutorial, we’ll build a form that sends public information to that API and a separate form that sends secret/sensitive information to Gaia Storage.

Blockstack applications use the Gaia storage system to store data on behalf of a user. When the user logs in to an application, the authentication process gives the application the URL of a Gaia hub, which performs writes on behalf of that user. The Gaia hub authenticates writes to a location by requiring a valid authentication token, generated by a private key authorized to write at that location.

By using Gaia Storage, the decentralized way of storing information:

  • Your data is more secure than traditional storage systems, which have one or a few central points of vulnerability.
  • Millions of encrypted copies of your data are spread across the world, constantly verifying each other for changes made without your authorization.
  • A hacker would need to compromise 51% of the blockchain in order to access your data. This would require more computing power than any known entity possesses.
  • You own your own data, your information will be safe and no one can access it, not me, not Blockstack, not even the president.

Prerequisites: Knowledge of setting up your own API. We’ll also be using React.js.

Coming from part two of this three-part tutorial series, this is what App.js looked like:

import React, { Component } from "react";
import { appConfig } from "./utils/constants";
import { UserSession } from "blockstack";
class App extends Component {
state = {
userSession: new UserSession({ appConfig }), // coming from Blockstack
userData: {}, // coming from Blockstack
users: [], // coming from your API
currentUser: {} // coming from your API
};
componentDidMount = async () => {
const { userSession } = this.state;
if (!userSession.isUserSignedIn() && userSession.isSignInPending()) {
const userData = await userSession.handlePendingSignIn();
if (!userData.username) {
throw new Error("This app requires a username");
}
window.location = "/";
}
this.getUsers();
};
handleSignIn = () => {
const { userSession } = this.state;
userSession.redirectToSignIn();
};
handleSignOut = () => {
const { userSession } = this.state;
userSession.signUserOut();
window.location = "/";
};
// We're fetching the users array from your API (make sure the path is correct)
// In your app's state, we're storing the userData object that comes from Blockstack when a user signs in
// We're searching for the username from userData in the users array,
// If that username exists in your API, then we store that user object in state
// Otherwise, we create a new user object with the username from userData
getUsers() {
const { userSession } = this.state;
fetch("http://localhost:3000/api/v1/users")
.then(res => res.json())
.then(users => {
if (userSession.isUserSignedIn()) {
const userData = userSession.loadUserData();
this.setState({
userData
});
let currentUser = users.find(
user => user.username === userData.username
);
if (currentUser) {
this.setState({ users, currentUser });
} else {
this.createUser(userData.username);
}
}
});
}
createUser = username => {
fetch("http://localhost:3000/api/v1/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify({
username
})
})
.then(res => res.json())
.then(user => {
let newArr = [...this.state.users, user];
this.setState({ users: newArr, currentUser: user });
});
};
render() {
const { userSession, currentUser } = this.state;
return (
<div className="App">
{userSession.isUserSignedIn() ? (
<div className="hello">
<h2>Hello {currentUser.username} !</h2>
<button className="button" onClick={this.handleSignOut}>
<strong>Sign Out</strong>
</button>
</div>
) : (
<button className="button" onClick={this.handleSignIn}>
<strong>Sign In</strong>
</button>
)}
</div>
);
}
}
export default App;
view raw App.js hosted with ❤ by GitHub

We’re going to add a form in JSX and some handler methods to use our API. We’ll, do the same for Gaia Storage but in the Blockstack way. Skip to step 6 if you only want to see the final code.

1) We’ll be adding a few placeholders to your app’s state:

state = {
userSession: new UserSession({ appConfig }), // coming from Blockstack
userData: {}, // coming from Blockstack
users: [], // coming from your API
currentUser: {}, // coming from your API
superhero: "", // I'M NEW coming from form input
gaiaUser: {}, // I'M NEW coming from Gaia Storage
crush: "" // I'M NEW coming from form input
};
view raw App.js hosted with ❤ by GitHub

2) Within the App class, we’ll add the changeHandler, submitHandler, and submitGaiaHandler functions for the forms (be sure to add the superhero attribute to your backend for the fetch PATCH request):

// I'M NEW
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
// I'M NEW, standard fetch method
submitHandler = e => {
const { superhero, currentUser } = this.state;
e.preventDefault();
// be sure to add the superhero attribute to the backend
fetch(`http://localhost:3000/api/v1/users/${currentUser.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify({
superhero
})
})
.then(res => res.json())
.then(data => {
this.setState({ currentUser: data });
console.log("API", data); // see that the data transferred
});
};
// I'M NEW, putFile is a method provided by the Blockstack library
submitGaiaHandler = e => {
const { userSession, crush } = this.state;
const user = { crush: crush };
let options = { encrypt: true };
e.preventDefault();
// encrypt and securely send your secret crush to Gaia Storage
userSession
.putFile("user.json", JSON.stringify(user), options)
.then(data => {
this.setState({ gaiaUser: user });
console.log("Gaia Storage", data); // see that the data is encrypted
});
// note that at this time, Blockstack only allows PUT but not PATCH
// you are replacing the entire gaiaUser object
};
view raw App.js hosted with ❤ by GitHub

3) Within the App class, we’ll add a function to retrieve data from Gaia Storage:

// I'M NEW, getFile is also a method provided by the Blockstack library
getGaiaUser = () => {
const { userSession } = this.state;
let options = { decrypt: true };
userSession.getFile("user.json", options).then(data => {
if (data) {
const user = JSON.parse(data);
this.setState({ gaiaUser: user });
} else {
const user = {};
this.setState({ gaiaUser: user });
}
});
};
view raw App.js hosted with ❤ by GitHub

4) Call getGaiaUser() function in componentDidMount():

componentDidMount = async () => {
const { userSession } = this.state;
if (!userSession.isUserSignedIn() && userSession.isSignInPending()) {
const userData = await userSession.handlePendingSignIn();
if (!userData.username) {
throw new Error("This app requires a username");
}
window.location = "/";
}
this.getUsers();
this.getGaiaUser(); // I'M NEW, find me
};
view raw App.js hosted with ❤ by GitHub

5) In render(), we’ll add the JSX for our new forms:

render() {
const { userSession, currentUser, superhero, crush, gaiaUser } = this.state;
let hero = currentUser.superhero;
let gaiaCrush = gaiaUser.crush;
return (
<div className="App">
{userSession.isUserSignedIn() ? (
<div className="hello">
<h2>Hello {currentUser.username} !</h2>
<button className="button" onClick={this.handleSignOut}>
<strong>Sign Out</strong>
</button>
<div className="forms">
{/* sending this information to public API */}
<div className="superhero">
<form onSubmit={this.submitHandler}>
<label htmlFor="superhero">
<h3>Who's your favorite superhero?</h3>
</label>
<input
id="superhero"
className="form-control"
name="superhero"
type="text"
placeholder="Ironman"
value={superhero}
onChange={this.changeHandler}
/>
<button className="button-small">
<strong>Submit to API</strong>
</button>
</form>
{hero && hero.toLowerCase() === "ironman" ? (
<h4>Good choice, {hero} is the best!</h4>
) : hero ? (
<p>{hero} is okay, but Ironman is the best!</p>
) : null}
<p>
This information is accessible to the public should you allow
other apps to fetch from your API.
</p>
</div>
<div className="crush">
{/* sending this information to Gaia Storage */}
<form onSubmit={this.submitGaiaHandler}>
<label htmlFor="crush">
<h3>Who's your current or childhood crush?</h3>
</label>
<input
id="crush"
className="form-control"
name="crush"
type="text"
placeholder="His/her name"
value={crush}
onChange={this.changeHandler}
/>
<button className="button-small">
<strong>Submit to Gaia Storage</strong>
</button>
</form>
{gaiaCrush ? (
<h4>{gaiaCrush} probably likes you too.</h4>
) : null}
<p>
Your secret is safe, only you have access to this information
and it is extremely difficult to hack.
</p>
</div>
</div>
</div>
) : (
<button className="button" onClick={this.handleSignIn}>
<strong>Sign In</strong>
</button>
)}
</div>
);
}
view raw App.js hosted with ❤ by GitHub

6) At the end of this process, App.js should look like this:

import React, { Component } from "react";
import { appConfig } from "./utils/constants";
import { UserSession } from "blockstack";
class App extends Component {
state = {
userSession: new UserSession({ appConfig }), // coming from Blockstack
userData: {}, // coming from Blockstack
users: [], // coming from your API
currentUser: {}, // coming from your API
superhero: "", // I'M NEW coming from form input
gaiaUser: {}, // I'M NEW coming from Gaia Storage
crush: "" // I'M NEW coming from form input
};
componentDidMount = async () => {
const { userSession } = this.state;
if (!userSession.isUserSignedIn() && userSession.isSignInPending()) {
const userData = await userSession.handlePendingSignIn();
if (!userData.username) {
throw new Error("This app requires a username");
}
window.location = "/";
}
this.getUsers();
this.getGaiaUser(); // I'M NEW, find me
};
handleSignIn = () => {
const { userSession } = this.state;
userSession.redirectToSignIn();
};
handleSignOut = () => {
const { userSession } = this.state;
userSession.signUserOut();
window.location = "/";
};
// We're fetching the users array from your API (make sure the path is correct)
// In your app's state, we're storing the userData object that comes from Blockstack when a user signs in
// We're searching for the username from userData in the users array,
// If that username exists in your API, then we store that user object in state
// Otherwise, we create a new user object with the username from userData
getUsers() {
const { userSession } = this.state;
fetch("http://localhost:3000/api/v1/users")
.then(res => res.json())
.then(users => {
if (userSession.isUserSignedIn()) {
const userData = userSession.loadUserData();
this.setState({
userData
});
let currentUser = users.find(
user => user.username === userData.username
);
if (currentUser) {
this.setState({ users, currentUser });
} else {
this.createUser(userData.username);
}
}
});
}
createUser = username => {
fetch("http://localhost:3000/api/v1/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify({
username
})
})
.then(res => res.json())
.then(user => {
let newArr = [...this.state.users, user];
this.setState({ users: newArr, currentUser: user });
});
};
// I'M NEW
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
// I'M NEW, standard fetch method
submitHandler = e => {
const { superhero, currentUser } = this.state;
e.preventDefault();
// be sure to add the superhero attribute to the backend
fetch(`http://localhost:3000/api/v1/users/${currentUser.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify({
superhero
})
})
.then(res => res.json())
.then(data => {
this.setState({ currentUser: data });
console.log("API", data); // see that the data transferred
});
};
// I'M NEW, putFile is a method provided by the Blockstack library
submitGaiaHandler = e => {
const { userSession, crush } = this.state;
const user = { crush: crush };
let options = { encrypt: true };
e.preventDefault();
// encrypt and securely send your secret crush to Gaia Storage
userSession
.putFile("user.json", JSON.stringify(user), options)
.then(data => {
this.setState({ gaiaUser: user });
console.log("Gaia Storage", data); // see that the data is encrypted
});
// note that at this time, Blockstack only allows PUT but not PATCH
// you are replacing the entire gaiaUser object
};
// I'M NEW, getFile is also a method provided by the Blockstack library
getGaiaUser = () => {
const { userSession } = this.state;
let options = { decrypt: true };
userSession.getFile("user.json", options).then(data => {
if (data) {
const user = JSON.parse(data);
this.setState({ gaiaUser: user });
} else {
const user = {};
this.setState({ gaiaUser: user });
}
});
};
render() {
const { userSession, currentUser, superhero, crush, gaiaUser } = this.state;
let hero = currentUser.superhero;
let gaiaCrush = gaiaUser.crush;
return (
<div className="App">
{userSession.isUserSignedIn() ? (
<div className="hello">
<h2>Hello {currentUser.username} !</h2>
<button className="button" onClick={this.handleSignOut}>
<strong>Sign Out</strong>
</button>
<div className="forms">
{/* sending this information to public API */}
<div className="superhero">
<form onSubmit={this.submitHandler}>
<label htmlFor="superhero">
<h3>Who's your favorite superhero?</h3>
</label>
<input
id="superhero"
className="form-control"
name="superhero"
type="text"
placeholder="Ironman"
value={superhero}
onChange={this.changeHandler}
/>
<button className="button-small">
<strong>Submit to API</strong>
</button>
</form>
{hero && hero.toLowerCase() === "ironman" ? (
<h4>Good choice, {hero} is the best!</h4>
) : hero ? (
<p>{hero} is okay, but Ironman is the best!</p>
) : null}
<p>
This information is accessible to the public should you allow
other apps to fetch from your API.
</p>
</div>
<div className="crush">
{/* sending this information to Gaia Storage */}
<form onSubmit={this.submitGaiaHandler}>
<label htmlFor="crush">
<h3>Who's your current or childhood crush?</h3>
</label>
<input
id="crush"
className="form-control"
name="crush"
type="text"
placeholder="His/her name"
value={crush}
onChange={this.changeHandler}
/>
<button className="button-small">
<strong>Submit to Gaia Storage</strong>
</button>
</form>
{gaiaCrush ? (
<h4>{gaiaCrush} probably likes you too.</h4>
) : null}
<p>
Your secret is safe, only you have access to this information
and it is extremely difficult to hack.
</p>
</div>
</div>
</div>
) : (
<button className="button" onClick={this.handleSignIn}>
<strong>Sign In</strong>
</button>
)}
</div>
);
}
}
export default App;
view raw App.js hosted with ❤ by GitHub

7) Let’s sprinkle some CSS on this in App.css and make it a little easier on the eyes:

.App {
display: flex;
text-align: center;
height: 100vh;
}
.hello {
margin: auto;
}
.button {
max-width: 100%;
max-height: 4em;
font-size: 1em;
margin: auto;
display: inline-block;
padding: 1em;
color: #000;
transition: color 0.3s linear;
border-color: #000;
border-width: 2px;
}
.button-small:hover {
color: white;
border: 2px solid white;
background-color: black;
}
.button-small {
max-width: 100%;
max-height: 4em;
font-size: 0.8em;
margin: auto;
display: inline-block;
padding: 0.5em;
color: #000;
transition: color 0.3s linear;
border-color: #000;
border-width: 2px;
}
.button:hover {
color: white;
border: 2px solid white;
background-color: black;
}
.forms {
display: flex;
margin-top: 5em;
}
.superhero,
.crush {
flex: 1;
padding: 0em 6em 0em 6em;
}
.form-control {
font-size: 1em;
padding: 0.35em;
border-radius: 0.3em 0em 0.3em 0.3em;
}
view raw App.css hosted with ❤ by GitHub

8) Test the two different forms, open console to see the data that is returned upon submitting each form.

forms

9) You should see encrypted information if you click the link that Gaia Storage returns on App.js:134:

console
json


Congratulations for making it to the end of this tutorial! You have now successfully implemented Blockstack authentication, connected Blockstack to a public API, and securely transferred data to Gaia Storage.

There is still lots to learn, but you now have the fundamentals to start building decentralized and hybrid apps. Remember you can always dive into the Blockstack documentation or reach out to the community on Slack if you get stuck.

app mining

Thank you for following along through all of the tutorials, reach out if you have any questions. Wish you the best in your blockchain endeavors!


Bring your friends and come learn JavaScript in a fun never before seen way! waddlegame.com

Image of Stellar post

How a Hackathon Win Led to My Startup Getting Funded

In this episode, you'll see:

  • The hackathon wins that sparked the journey.
  • The moment José and Joseph decided to go all-in.
  • Building a working prototype on Stellar.
  • Using the PassKeys feature of Soroban.
  • Getting funded via the Stellar Community Fund.

Watch the video 🎥

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay