Never Use Passwords Again with Ethereum and Metamask

Written by asmiller1989 | Published 2017/03/23
Tech Story Tags: javascript | nodejs

TLDRvia the TL;DR App

I build a lot of proofs of concept at ConsenSys for various clients and usually they want something that leverages the Ethereum blockchain to solve some business use case. Strangely, these systems often get designed with standard web logins (i.e. with a username and password).

I always ask myself why I’m designing things this way since, after all, this is an annoying aspect of every web application that Ethereum can solve today.So I’m finally putting my foot down and designing that solution.

JSON Web Tokens

One very popular way to log into a standard web system (and/or use its API) is to submit a password (which is hashed client side) to an authentication endpoint and receive a token in return. This is (usually) called a JSON Web Token and is typically valid for some finite period of time (minutes to days). Here is a nice tutorial on a standard implementation.

JSON Web Tokens are nice and dandy, but I got to thinking that it’s very easy to authenticate yourself on the blockchain. In fact, when you use Ethereum, you are constantly doing it.

If you think of an Ethereum address (which is just a sha3 hash of your public key) as an account on a website, it is very easy to prove you own that account by signing a piece of data with your private key. This data is arbitrary and can be any random string that the website’s API serves up. Thus, we can use an address as a username and bypass the need for a password. In fact, we don’t even need to use the blockchain to do this.

Here’s what it looks like using Express:

First we need to do an elliptic curve signature with a private key:

var ethUtil = require(‘ethereumjs-util’); // >=5.1.1

var data = ‘i am a string’;// Elliptic curve signature must be done on the Keccak256 Sha3 hash of a piece of data.var message = ethUtil.toBuffer(data);var msgHash = ethUtil.hashPersonalMessage(message);var sig = ethUtil.ecsign(msgHash, privateKey);var serialized = ethUtil.bufferToHex(this.concatSig(sig.v, sig.r, sig.s))return serialized

Don’t worry too much about what these parameters are yet. There is some cryptography going on here and I encourage you to read up on elliptic curve signatures. The Bitcoin wiki is a decent place to start.

Anyway, once we have our signature components, we can package them along with the user’s address and send it all to an authentication endpoint.

POST /Authenticate

var jwt = require(‘jsonwebtoken’);var ethUtil = require('ethereumjs-util');

function checkSig(req, res) {var sig = req.sig;var owner = req.owner;

// Same data as beforevar data = ‘i am a string’;

var message = ethUtil.toBuffer(data)var msgHash = ethUtil.hashPersonalMessage(message)

// Get the address of whoever signed this messagevar signature = ethUtil.toBuffer(sig)var sigParams = ethUtil.fromRpcSig(signature)var publicKey = ethUtil.ecrecover(msgHash, sigParams.v, sigParams.r, sigParams.s)var sender = ethUtil.publicToAddress(publicKey)var addr = ethUtil.bufferToHex(sender)

// Determine if it is the same address as 'owner'var match = false;if (addr == owner) { match = true; }

if (match) {// If the signature matches the owner supplied, create a// JSON web token for the owner that expires in 24 hours.var token = jwt.sign({user: req.body.addr}, ‘i am another string’, { expiresIn: “1d” });res.send(200, { success: 1, token: token })} else {// If the signature doesn’t match, error outres.send(500, { err: ‘Signature did not match.’});}

}

So basically, given some piece of data, an address, and the components of an EC signature, we can cryptographically prove that the address belongs to the person who signed the data. Pretty cool, huh?

Once we are satisfied the signature and address match, we can sign a JSON Web Token for that address server side. In this case, the token is valid for 1 day.

Now we just need to put in some middleware to guard any routes that would be serving or modifying protected information.

middleware/auth.js

function auth(req, res, next) {jwt.verify(req.body.token, ‘i am another string’, function(err, decoded) {if (err) { res.send(500, { error: ‘Failed to authenticate token.’}); }else {req.user = decoded.user;next();};});}

app.js

// Routesapp.post(‘/UpdateData’, auth, Routes.UpdateData);…

If the provided token corresponds to the user who sent the request, we continue to requested route. Note that the middleware modifies the request. It is this new `user` parameter that we need to reference because we know it got set in our middleware.

POST /UpdateData

function UpdateData(req, res) {// Only use the user that was set in req by auth middleware!var user = req.user;updateYourData(user, req.body.data);...}

And there we have it! Your user has literally signed in, but didn’t need a password.

UI stuff

But how does a user actually sign this data in the browser? Metamask to the rescue! Metamask is a neat chrome extension that injects web3 into your browser window.

mycomponent.jsx

makeSig(dispatch) {

function toHex(s) {var hex = ‘’;for(var i=0;i<s.length;i++) { hex += ‘’+s.charCodeAt(i).toString(16); }return `0x${hex}`;}

var data = toHex(‘i am a string’);web3.currentProvider.sendAsync({ id: 1, method: 'personal_sign', params: [web3.eth.accounts[0], data] },function(err, result) {let sig = result.result;dispatch(exchange.authenticate(sig, user))})}}

render(){let { dispatch, _main: { sig } } = this.props;if (Object.keys(sig).length == 0) { this.makeSig(dispatch); }return (<p>I am a webpage</p>);}

This will trigger Metamask to pop up a window asking the user to sign the message:

Once the callback is invoked, it will call the following action:

authenticate(sig, user) {return (dispatch) => {fetch(`${this.api}/Authenticate`, {method: 'POST',body: JSON.stringify({ owner: user, sig: sig}),headers: { "Content-Type": "application/json" }}).then((res) => { return res.text(); }).then((body) => {var token = JSON.parse(body).token;dispatch({ type: 'SET_AUTH_TOKEN', result: token})})}}

And once you have the auth token saved in your reducer, you can call your authenticated endpoints. And there we have it!

Note that v, r, and s values must be recovered from the signature. Metamask has a signature util module that shows how the signature is constructed. It can be deconstructed like this:

var solidity_sha3 = require('solidity-sha3').default;

let hash = solidity_sha3(data);let sig = result.result.substr(2, result.result.length);let r = sig.substr(0, 64);let s = sig.substr(64, 64);let v = parseInt(sig.substr(128, 2));

where r will be parsed as either 0 or 1. Note also that this uses the solidity-sha3 module to make sure this hashing algorithm is the same one being used as solidity’s native hashing method (we are hashing the hex-string that was signed earlier).

Production ready

I can’t emphasize enough that every web application using JSON web tokens could easily take advantage of this today. Any user with a Metamask extension could simply bypass the login screen with arguably better security than whatever you’re currently using to manage logins. This means fewer forgotten passwords, less wasted time, and a happier user base.

And, you know, if you want your users to pay each other (or you, or users on on any other system that uses this) without an intermediary or if you want to take advantage of Ethereum’s million other features, this sets you up to do that too.

Make the switch today. Join us Ethereum folk and conquer the world.

— — — —

The views expressed by the author above do not necessarily represent the views of Consensus Systems LLC DBA Consensys. ConsenSys is a decentralized community with ConsenSys Media being a platform for members to freely express their diverse ideas and perspectives. To learn more about ConsenSys and Ethereum, please visit our website. And if you liked this piece, sign up here for our weekly newsletter.


Published by HackerNoon on 2017/03/23