In the last post, I introduced Blockstack and discussed the two critical components for building decentralized apps, or dapps, on the platform. In this post, we’ll bootstrap our dapp for web development snippets with user authentication and authorization powered by blockstack.js, the JavaScript library for interacting with Blockstack.
When using blockstack.js for user login, you’ll need to present the user with a log-in button of sorts. When the user clicks the button, you call a library method that redirects them to a separate tab either driven by the Blockstack Browser app, if installed, or the web-based Blockstack log-in if the user doesn’t have the Blockstack Browser installed.
Once a user chooses their preferred ID or signs up for an account, they’ll be redirected to your app’s callback handler where you can get basic user information from the user and make other API calls on their behalf, such as saving or retrieving application information from their personal storage.
Bootstrapping the project
Blockstack.js is just a library, so you don't need to worry about compatibility with whatever front-end tools you might be using. I'll be using React for this project, so the initial project bootstrapping will be done with create-react-app
. In addition to React, I’m also going to be using Redux to manage state and react-router to manage the single-page app. Then, you just need to install the blockstack
dependency with npm
or yarn
.
The actual code needed to redirect to the Blockstack Browser is a simple method call blockstack.redirectToSignIn()
. For the sake of simplifying this example, I’ve added a single button to the bootstrapped App
component that invokes that method when clicked:
import React, { Component } from 'react';
import './App.css';
import * as blockstack from 'blockstack';
class App extends Component {
render() {
return (
<div className="App">
<input type="button" onClick={() => blockstack.redirectToSignIn()} value="Sign In With Blockstack" />
</div>
);
}
}
export default App;
This renders a blank page with a single button on it. However, when you click the button, you may see an issue here:
Blockstack displays application information and icons in the log-in screen by pulling the manifest.json file from your app. This requires cross-domain requests to be allowed on that file. However, create-react-app very sensibly does not enable CORS in development. The quickest way to enable CORS for that file is to enable CORS for the whole app, which you can do by installing cors
with npm
or yarn
and adding a file with the following contents at src/setupProxy.js.
const cors = require('cors');
module.exports = (app) => {
app.use(cors());
};
This trick piggybacks on a create-react-app
mechanism for having more control over proxying requests in Express, which react-scripts
uses behind the scenes. Restarting the development server will load that configuration and then we can see our sample application configuration loaded in the log-in screen.
Once you select an ID, Blockstack redirects you back to your app’s URL with a JSON web token (JWT) in the query string. We’re not handling that JWT yet, but blockstack.js provides some convenient methods for pulling that data in and decoding it. At this point in the process, our user sign-in is considered “pending,” since we have the token but haven’t done anything with it. Once we decode and validate the token, which is an asynchronous process the library handles for us, we’ll have access to the signed-in user.
Checking for signed-in users
The blockstack.js library also configures the signed-in user in local storage, so subsequent page loads won’t require the user to log in again. We’ll modify our base component to check if a user sign-in is pending or a user is already signed in. The first step is to create some state on our component that holds user data and check for pending or completed user sign-ins when the component is created. We’ll do that in a constructor method for now:
constructor() {
super();
let user = null;
if (blockstack.isUserSignedIn()) {
user = blockstack.loadUserData();
}
if (blockstack.isSignInPending()) {
blockstack.handlePendingSignIn()
.then(userData => this.setState({user: userData}));
}
this.state = {
user,
};
}
Our render
method is about to get a bit more complicated, so we’ll break it into a few separate methods for clarity. Then, within the render
method, we’ll check the state to see if a user is present and render a log-in button or a simple profile accordingly:
renderLogin() {
return <input type="button" onClick={() => blockstack.redirectToSignIn()} value="Sign In With Blockstack" />
}
renderProfile() {
const user = new blockstack.Person(this.state.user.profile);
return (
<div>
<h1>{user.name()}</h1>
<h3>{user.description()}</h3>
<input type="button" onClick={() => blockstack.signUserOut()} value="Log Out" />
</div>
);
}
render() {
return (
<div className="App">
{this.state.user ? this.renderProfile() : this.renderLogin()}
</div>
);
}
The result is an underwhelmingly designed, but fully functional dapp sign-in process, with full access to profile information.
And there you are: User authentication and authorization without spinning up a back-end, powered by the blockchain! Next week we’ll be integrating decentralized storage with blockstack.js so our users can save app data and settings across devices and log-ins while maintaining full control of their information. If you want to take a look at the work in progress, the code for this week is available on GitHub. As always, continue the conversation on Twitter: @freethejazz.