You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

10 KiB

description
Single-page application with Blockstack

Tutorial for App Integration

{:.no_toc}

In this tutorial, you will learn about Blockstack authentication and storage by installing, running and reviewing the code for a "Todos" web app built with Blockstack and React.

This app highlights the following platform functionality:

  • Generate Secret Key with associated Blockstack username to authenticate app
  • Add, edit and delete encrypted app data with Gaia
  • Decrypt data on Gaia for public sharing by URL
  • Unauthenticate and re-authenticate app with Secret Key

Preview the app or view its code on GitHub.

Existing familiarity with React is recommended for reviewing this app's code.

  • TOC {:toc}

Install and run the app

You must have recent versions of Git and Node.js (v12.10.0 or greater) installed already.

  1. Install the code and its dependencies:

    git clone https://github.com/blockstack/blockstack-todos
    cd blockstack-todos
    npm install
    
  2. Run the application:

    $ npm run start
    

    You should see output similar to the following:

      Compiled successfully!
    
      You can now view bs-todo in the browser.
    
        http://127.0.0.1:3000/
    
      Note that the development build is not optimized.
      To create a production build, use npm run build.
    
  3. Open your local browser to http://localhost:3000 if it doesn't open automatically.

    You should see the app's landing page:

Onboard into your first Blockstack app

  1. Choose Get started to start onboarding into the app.

    The app displays a standardized introductory modal using Blockstack Connect, a JavaScript library that makes it easy to integrate Blockstack into the UI of any web app.

    The following React component triggers this modal in src/components/Signin.js:

    import React from 'react';
    import '../styles/Signin.css'
    import { useConnect } from '@blockstack/connect';
    
    export const Signin = () => {
      const { doOpenAuth } = useConnect();
    
      return (
        <div className="panel-landing" id="section-1">
          <h1 className="landing-heading">Hello, Blockstack!</h1>
          <p className="lead">
            <button
              className="btn btn-primary btn-lg"
              id="signin-button"
              onClick={() => doOpenAuth()}
            >
              Sign In with Blockstack
            </button>
          </p>
        </div>
      );
    }
    
    export default Signin;
    
    

    This component imports the React hook useConnect from the Blockstack Connect library.

    useConnect returns many helper functions such as doOpenAuth, which triggers this modal upon click of the "Get started" button.

    The modal is designed to prepare new users for a different type of relationship with Blockstack apps, one in which they authenticate with a Secret Key that's used to encrypt their private data.

    The modal displays the app's name and icon as configured in src/components/App.js:

    
      appDetails: {
        name: 'Blockstack App',
        icon: window.location.origin + '/favicon.ico'
      }
    
    

    This component loads the UserSession module from a second Blockstack library called blockstack.js, which complements Blockstack Connect by providing an API for many protocol-level operations, such as for authentication and storage.

    
    import { UserSession } from 'blockstack';
    import { appConfig } from '../assets/constants'
    
    ...
    
    const userSession = new UserSession({ appConfig })
    
    

    This module handles user session operations and is initiated using the appConfig object, which contains an array of scopes that indicate just what permissions to grant during authentication:

    export const appConfig = new AppConfig(['store_write', 'publish_data'])
    

    The appDetails and userSession objects are joined by the callback function finished in configuring Blockstack Connect for authentication with the authOptions object:

    finished: ({ userSession }) => {
      this.setState({ userData: userSession.loadUserData() });
    }
    
    

    This function simply saves data about the user into the app's state upon authentication.

    Further down in the component we see in componentDidMount that it checks upon mount to either process completion of authentication with userSession.handlePendingSignIn() or otherwise load session data into app state as above with userSession.isUserSignedIn():

    componentDidMount() {
      if (userSession.isSignInPending()) {
        userSession.handlePendingSignIn().then((userData) => {
          window.history.replaceState({}, document.title, "/")
          this.setState({ userData: userData})
        });
      } else if (userSession.isUserSignedIn()) {
        this.setState({ userData: userSession.loadUserData() });
      }
    }
    
  2. Choose Get started to generate a Secret Key.

    The app triggers a popup window in which the Blockstack App is loaded from app.blockstack.org and begins generating a new Secret Key.

  3. Choose Copy Secret Key to copy your Secret Key to the clipboard.

    The Secret Key is a unique 12-word mnemonic phrase that empowers the user not only to access Blockstack apps securely and independently. It's also used to encrypt all of the private data they create and manage with Blockstack apps.

    Secret Keys are like strong passwords. However, they can never be recovered if lost or reset if stolen. As such, it's paramount that users handle them with great care.

  4. Choose I've saved it to confirm you've secured your Secret Key in a suitable place.

  5. Enter a username value and choose Continue

    The username will be used by the app to generate a URL for sharing your todos, should you choose to make them public.

    It is registered on the Stacks blockchain with the Blockstack Naming System (BNS) and associated with your Secret Key.

  6. You've now completed onboarding into the app!

Add, edit and delete todos privately

Once you've authenticated the app, you can can start adding todos by entering values into the "Write your to do" field and hitting "Enter".

The data for all todos are saved as JSON to the Gaia hub linked to your Secret Key using the putFile method of the userSession object in the src/components/Profile.js component:

saveTasks(tasks, encrypt) {
  const options = { encrypt: encrypt ? true : encrypt };
  this.props.userSession.putFile(TASKS_FILENAME, JSON.stringify(tasks), options);
}

These todos are subsequently loaded using the getFile method of the same object in the same component:

loadTasks() {
  const options = { decrypt: true };
  this.props.userSession.getFile(TASKS_FILENAME, options)
  .then((content) => {
    if(content) {
      const tasks = JSON.parse(content);
      this.setState({tasks});
    } 
  })
}

By default, the putFile and getFile methods automatically encrypt data when saved and decrypt it when retrieved, using the user's Secret Key. This ensures that only the user has the ability to view this data.

When deleting a todo, the same putFile method is used to save a new JSON array of todos that excludes the deleted todo.

Publish your todos publicly

If you wish to make your todos accessible to the public for sharing via URL, select "Make public".

This will call the makePublic method of the Profile.js component, which in turn calls saveTasks with the encrypt parameter set to false, which is used to disable encryption when using putFile:

makePublic() {
  const tasks = remove(e.currentTarget.dataset.index, this.state);
  this.saveTasks(tasks, false);
}

saveTasks(tasks, encrypt) {
  const options = { encrypt: encrypt ? true : encrypt };
  this.props.userSession.putFile(TASKS_FILENAME, JSON.stringify(tasks), options);
}

The app will now show all of your todos to anyone who visits the URL displayed with your Blockstack username as a suffix.

Sign out and back in

Signout is handled in src/components/App.js.

  handleSignOut(e) {
    e.preventDefault();
    userSession.signUserOut(window.location.origin);
  }