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.
 
 

379 lines
24 KiB

<p>In this tutorial, you build the code for and run a single-page application (SPA)
with Blockstack and Vue.js. Once the application is running, you take a tour
through the applications’ Blockstack functionality. You’ll learn how it manages
authentiation using a Blockstack ID and how it stores information associated
with that ID using Blockstack Storage (Gaia).</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>Make sure you have <a href="ids-introduction#create-an-initial-blockstack-id">created at least one Blockstack ID</a>. You’ll use this ID to Finteract with the Todo application.</p>
<p>The applicaton code relies on both the <code class="highlighter-rouge">npm</code> and the <code class="highlighter-rouge">yarn</code> package managers.
Before you begin, verify you have these tools <code class="highlighter-rouge">npm</code> using the <code class="highlighter-rouge">which</code> command to
verify.</p>
<div class="language-bash highlighter-rouge"><pre class="highlight"><code><span class="gp">$ </span>which npm
/usr/local/bin/npm
<span class="gp">$ </span>which yarn
/usr/local/bin/yarn
</code></pre>
</div>
<p><a href="https://www.npmjs.com/get-npm">Install npm</a>, <a href="https://yarnpkg.com/lang/en/docs/install/#mac-stable">install
yarn</a>, or both as needed. You</p>
<p>While it stands alone, this tour does on the information from the <a href="hello-blockstack">Hello
Blockstack tutorial</a>. If you haven’t worked through that
tutorial, you may want to do that before continuing.</p>
<h2 id="install-the-applicaton-code-and-retrieve-the-dependencies">Install the applicaton code and retrieve the dependencies</h2>
<p>You can clone the source code with <code class="highlighter-rouge">git</code> or <a href="https://github.com/blockstack/blockstack-todos/archive/master.zip">download and unzip the code from
the
repository</a>.
These instructions assume you are cloning.</p>
<ol>
<li>
<p>Install the code by cloning it.</p>
<div class="highlighter-rouge"><pre class="highlight"><code> $ git clone git@github.com:blockstack/blockstack-todos.git
</code></pre>
</div>
</li>
<li>
<p>Change to directory to the root of the code.</p>
<div class="highlighter-rouge"><pre class="highlight"><code> $ cd blockstack-todos
</code></pre>
</div>
</li>
<li>
<p>Use yarn to install the dependencies.</p>
<div class="highlighter-rouge"><pre class="highlight"><code> $ yarn install
yarn install v1.9.2
info No lockfile found.
...
[4/5] 🔗 Linking dependencies...
[5/5] 📃 Building fresh packages...
success Saved lockfile.
✨ Done in 19.90s.
</code></pre>
</div>
</li>
</ol>
<h2 id="understand-the-important-application-files">Understand the important application files</h2>
<p>The Todo application has a basic Vue.js structure. There are several configuration files but the central programming files are in the <code class="highlighter-rouge">src</code> directory:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">main.js</code></td>
<td>Application initialization.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">App.vue </code></td>
<td>Code for handling the <code class="highlighter-rouge">authResponse</code>.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">Landing.vue </code></td>
<td>Code for the initial sign on page.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">Dashboard.vue</code></td>
<td>Application data storage and user sign out.</td>
</tr>
</tbody>
</table>
<p>The example application runs in a node server on your local host. In the next section, you start the application and interact with it.</p>
<ol>
<li>
<p>Start the application.</p>
<div class="highlighter-rouge"><pre class="highlight"><code> $ npm run start
</code></pre>
</div>
<p>You should see a simple application:</p>
<p><img src="images/todo-sign-in.png" alt="" /></p>
</li>
<li>
<p>Choose <strong>Sign In with Blockstack</strong>.</p>
</li>
</ol>
<h2 id="understand-the-sign-in-process">Understand the sign in process</h2>
<p>At startup, the Todo application detects whether the user has the Blockstack client edition
installed or not. This is done automatically by the Blockstack API, more
about this later. What the authenticator displays depends on which whether the user has installed the Blockstack Authenticator client edition or not.</p>
<table>
<thead>
<tr>
<th>Client edition installed</th>
<th>Not installed</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="images/login.gif" alt="" /></td>
<td><img src="images/login-choice.png" alt="" /></td>
</tr>
</tbody>
</table>
<p>If the user was logged into the Blockstack authenticator (web or client) but
did not reset it, the web application to use the current identity:</p>
<p><img src="images/login-no-auth.png" alt="" /></p>
<p>If the user chooses <strong>Deny</strong>, the Blockstack authenticator opens but the user
is not logged into the sample application.</p>
<p><img src="images/windows-browser.png" alt="" /></p>
<p>If the login to the application is successful, the user is presented with the application:</p>
<p><img src="images/todo-app.png" alt="" /></p>
<p>Clicking the <strong>Sign In With Blockstack</strong> button brings up a modal that prompts
you to use an existing ID’s session, create a new ID, or reset the browser with
another ID. When Blockstack is provided an ID, it generates an <em>ephemeral key</em>
within the application. An ephemeral key is generated for each execution of a
key establishment process. This key is just used for the particular instance of
the application, in this case to sign a <strong>Sign In</strong> request.</p>
<p>Blockstack also generates a public key token which is sent to the authenticator
as an <code class="highlighter-rouge">authRequest</code> from the authenticator to your local blockstack-core node.
The signed authentication request is sent to Blockstack through a JSON Web
Token. The JWT is passed in via a URL query string in the <code class="highlighter-rouge">authRequest</code>
parameter:
<code class="highlighter-rouge">https://browser.blockstack.org/auth?authRequest=j902120cn829n1jnvoa...</code>. To
decode the token and see what information it holds:</p>
<ol>
<li>Copy the <code class="highlighter-rouge">authRequest</code> string from the URL.</li>
<li>Navigate to <a href="http://jwt.io/">jwt.io</a>.</li>
<li>
<p>Paste the full token there.</p>
<p>The output should look similar to below:</p>
<div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nt">"jti"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3i96e3ad-0626-4e32-a316-b243154212e2"</span><span class="p">,</span><span class="w">
</span><span class="nt">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1533136622</span><span class="p">,</span><span class="w">
</span><span class="nt">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1533140228</span><span class="p">,</span><span class="w">
</span><span class="nt">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"did:btc-addr:1Nh8oQTunbEQWjrL666HBx2qMc81puLmMt"</span><span class="p">,</span><span class="w">
</span><span class="nt">"public_keys"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"0362173da080c6e1dec0653fa9a3eff5f5660546e387ce6c24u04a90c2fe1fdu73"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nt">"domain_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:8080"</span><span class="p">,</span><span class="w">
</span><span class="nt">"manifest_uri"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:8080/manifest.json"</span><span class="p">,</span><span class="w">
</span><span class="nt">"redirect_uri"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:8080/"</span><span class="p">,</span><span class="w">
</span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.2.0"</span><span class="p">,</span><span class="w">
</span><span class="nt">"do_not_include_profile"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nt">"supports_hub_url"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nt">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"store_write"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
</li>
</ol>
<blockquote>
<p><strong>Note</strong>:</p>
<ol>
<li>The <code class="highlighter-rouge">iss</code> property is a decentralized identifier or <code class="highlighter-rouge">did</code>. This identifies you and your name to the application. The specific <code class="highlighter-rouge">did</code> is a <code class="highlighter-rouge">btc-addr</code>.</li>
<li>The Blockstack JWT implementation is different from other implementations because of the underlying cryptography we employ. There are libraries in <a href="https://github.com/blockstack/jsontokens-js">Javascript</a> and <a href="https://github.com/blockstack/ruby-jwt-blockstack">Ruby</a> available on the Blockstack Github to allow you to work with these tokens.</li>
</ol>
</blockquote>
<p>When the blockstack-core receives the <code class="highlighter-rouge">authRequest</code>, it generates a session token and
returns an authentication response to the application. This response is similar
to the <code class="highlighter-rouge">authRequest</code> above in that the <code class="highlighter-rouge">authResponse</code> includes a private key
intended only for the application. This allows the application to encrypt data
on your personal Blockstack storage.</p>
<p>You are now logged into the Todo application!</p>
<h2 id="undder-the-covers-in-the-sign-in-code">Undder the covers in the sign in code</h2>
<p>Now, go to the underlying <code class="highlighter-rouge">blockstack-todo</code> code you cloned or downloaded. Sign
in and sign out is handled in each of these files:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">App.vue </code></td>
<td>Handles the <code class="highlighter-rouge">authResponse</code>.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">Landing.vue </code></td>
<td>Generates the <code class="highlighter-rouge">authRequest</code>.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">Dashboard.vue</code></td>
<td>Handles sign out.</td>
</tr>
</tbody>
</table>
<p>The <code class="highlighter-rouge">src/components/Landing.vue</code> code calls a <a href="https://blockstack.github.io/blockstack.js#redirectToSignIn"><code class="highlighter-rouge">redirectToSignIn()</code></a> function which generates the <code class="highlighter-rouge">authRequest</code> and redirects the user to the Blockstack authenticator:</p>
<div class="language-js highlighter-rouge"><pre class="highlight"><code><span class="nx">signIn</span> <span class="p">()</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">blockstack</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">blockstack</span>
<span class="nx">blockstack</span><span class="p">.</span><span class="nx">redirectToSignIn</span><span class="p">()</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Once the user authenticates, the application handles the <code class="highlighter-rouge">authResponse</code> in the <code class="highlighter-rouge">src/App.vue</code> file. :</p>
<div class="language-js highlighter-rouge"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">blockstack</span><span class="p">.</span><span class="nx">isUserSignedIn</span><span class="p">())</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">blockstack</span><span class="p">.</span><span class="nx">loadUserData</span><span class="p">().</span><span class="nx">profile</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">blockstack</span><span class="p">.</span><span class="nx">isSignInPending</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">blockstack</span><span class="p">.</span><span class="nx">handlePendingSignIn</span><span class="p">()</span>
<span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">userData</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span>
<span class="p">})</span>
<span class="p">}</span>
</code></pre>
</div>
<p>If <a href="https://blockstack.github.io/blockstack.js/#isusersignedin"><code class="highlighter-rouge">blockstack.isUserSignedIn()</code></a> is true, the user was previously signed in so Blockstack pulls the data from the browser and uses it in our application. If the check on <a href="https://blockstack.github.io/blockstack.js/#issigninpending"><code class="highlighter-rouge">blockstack.isSignInPending()</code></a> is true, a previous <code class="highlighter-rouge">authResponse</code> was sent to the application but hasn’t been processed yet. The <code class="highlighter-rouge">handlePendingSignIn()</code> function processes any pending sign in.</p>
<p>Signout is handled in <code class="highlighter-rouge">src/components/Dashboard.vue</code>.</p>
<div class="language-js highlighter-rouge"><pre class="highlight"><code><span class="nx">signOut</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">blockstack</span><span class="p">.</span><span class="nx">signUserOut</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>The method allows the application creator to decide where to redirect the user upon Sign Out:</p>
<h2 id="working-with-the-application">Working with the application</h2>
<p>Now trying adding a few todos using the application. For example, try making a list of applications you want to see built on top of Blockstack:</p>
<p><img src="images/make-a-list.png" alt="" /></p>
<p>Each list is immediately stored in the Gaia Hub linked to your Blockstack ID.
For more information about the Gaia hub, see the <a href="https://github.com/blockstack/gaia">hub
repository</a>. You can fetch the <code class="highlighter-rouge">todos.json</code>
file you just added by opening the Javascript console and running the following
command:</p>
<pre><code class="language-Javascript">blockstack.getFile("todos.json", { decrypt: true }).then((file) =&gt; {console.log(file)})
</code></pre>
<p>You should see a JSON with the todos you just added:</p>
<div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nt">"id"</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nt">"text"</span><span class="p">:</span><span class="s2">"Software package manager secured by the blockchain"</span><span class="p">,</span><span class="w">
</span><span class="nt">"completed"</span><span class="p">:</span><span class="kc">false</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nt">"id"</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nt">"text"</span><span class="p">:</span><span class="s2">"Mutable torrents with human readable names"</span><span class="p">,</span><span class="w">
</span><span class="nt">"completed"</span><span class="p">:</span><span class="kc">false</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nt">"id"</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nt">"text"</span><span class="p">:</span><span class="s2">"Decentralized twitter"</span><span class="p">,</span><span class="w">
</span><span class="nt">"completed"</span><span class="p">:</span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre>
</div>
<p>Now, add another todo and check it off. When you fetch the newly generated file
using the Javascript console it will reflect the change look for <code class="highlighter-rouge">"completed":true</code>:</p>
<div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nt">"id"</span><span class="p">:</span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nt">"text"</span><span class="p">:</span><span class="s2">"Blockstack Todo"</span><span class="p">,</span><span class="w">
</span><span class="nt">"completed"</span><span class="p">:</span><span class="kc">true</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nt">"id"</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nt">"text"</span><span class="p">:</span><span class="s2">"Software package manager secured by the blockchain"</span><span class="p">,</span><span class="w">
</span><span class="nt">"completed"</span><span class="p">:</span><span class="kc">false</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre>
</div>
<p>Now that you have seen the application in action, dig into how it works.</p>
<h2 id="implementing-storage">Implementing storage</h2>
<p>Now, go to the underlying <code class="highlighter-rouge">blockstack-todo</code> code you cloned or downloaded. The
application interactions with your Gaia Hub originate in the
<code class="highlighter-rouge">src/components/Dashboard.vue</code> file. First lets see where the changes to the
Todos are processed:</p>
<div class="language-js highlighter-rouge"><pre class="highlight"><code><span class="nx">todos</span><span class="err">:</span> <span class="p">{</span>
<span class="nl">handler</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">todos</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">blockstack</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">blockstack</span>
<span class="c1">// encryption is now enabled by default</span>
<span class="k">return</span> <span class="nx">blockstack</span><span class="p">.</span><span class="nx">putFile</span><span class="p">(</span><span class="nx">STORAGE_FILE</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">todos</span><span class="p">))</span>
<span class="p">},</span>
<span class="nx">deep</span><span class="err">:</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Tje <code class="highlighter-rouge">todos</code> JSON object is passed in and the <a href="https://blockstack.github.io/blockstack.js/#putfile"><code class="highlighter-rouge">blockstack.putFile()</code></a> method to store it in our Gaia Hub.</p>
<p>The code needs to read the Todo items from the storage with the <a href="https://blockstack.github.io/blockstack.js/#getfile"><code class="highlighter-rouge">blockstack.getFile()</code></a> method which returns a promise:</p>
<div class="language-js highlighter-rouge"><pre class="highlight"><code><span class="nx">fetchData</span> <span class="p">()</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">blockstack</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">blockstack</span>
<span class="nx">blockstack</span><span class="p">.</span><span class="nx">getFile</span><span class="p">(</span><span class="nx">STORAGE_FILE</span><span class="p">)</span> <span class="c1">// decryption is enabled by default</span>
<span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">todosText</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">todos</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">todosText</span> <span class="o">||</span> <span class="s1">'[]'</span><span class="p">)</span>
<span class="nx">todos</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">todo</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">todo</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">index</span>
<span class="p">})</span>
<span class="k">this</span><span class="p">.</span><span class="nx">uidCount</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">length</span>
<span class="k">this</span><span class="p">.</span><span class="nx">todos</span> <span class="o">=</span> <span class="nx">todos</span>
<span class="p">})</span>
<span class="p">},</span>
</code></pre>
</div>
<p>The <code class="highlighter-rouge">todos</code> data is retrieved from the promise.</p>
<h2 id="summary">Summary</h2>
<p>You now have everything you need to construct complex applications complete with authentication and storage on the Decentralized Internet. Why not try coding <a href="multi-player-storage.md">a sample application that accesses multiple profiles</a>.</p>
<p>If you would like to explore the Blockstack APIs, you can visit the <a href="https://core.blockstack.org/">Blockstack Core API</a> documentation or the <a href="https://blockstack.github.io/blockstack.js">Blockstack JS API</a>.</p>
<p>Go forth and build!</p>