A common design for Decentralized Application (DApp) consist to rely not only on Ethereum blockchain but on an API layer too. In this scenario, the DApp interact with the smart-contract through user’s Ethereum account and to the API layer through a JWT Token issued in exchange of user credentials.
The goal is to use Ethereum account as user credentials to request the JWT token.
A possible simplest way to do so could be to requests the user to make a transaction on Ethereum with an additional random generated data and then check the transaction and the random data before issuing the JWT. This approach has several side effect:
- The user must do a transaction and pay gas for a simple authentication.
- The user must wait for 12–120 seconds (based on gas spent) to complete the authentication process.
- All the login operations of every user became immutably public on Ethereum blockchain.
This way is not practical and has several user experience limitation, we need a way for a user to prove that she/he own the private key related to the account she/he wants to use to sign in, without (of course) asking for the private key and without asking her/him to make a transaction.
This tutorial was inspired to me by this post from Metamask team member Dan Finlay. Basically, your DApp could prompt a user to sign a text message using his private key. This sign operation doesn’t generate a transaction and it is handled by Metamask add-on transparently (btw, your account need to be unlocked). Once signed, the account, the message, and the signature are sent to the API Token endpoint. The authentication method first extrapolates the account (aka the public key) from the signature by a function that accepts as input the signature and the plain message. If the computed Ethereum address is equal to the account provided by the user, a JWT token is issued for that account.
Is important to note that the entire authentication flow is made without asking for a username/password or OAuth external service. The mechanism used to authenticate the user, is the same Ethereum use to guarantee security in Ethereum blockchain. This is possible thanks to web3.personal.sign JSON RPC provided by Go Ethereum (Geth) through Metamask plugin.
- From DApp user click the login button. This requires the web3 object provided by Metamask.
- Metamask asks the user to sign a message via web3.personal.sign JSON RPC.
- The signature is sent to the API layer that verifies the account via web3.personal.ecrecover JSON RPC.
- Once verified, the API layer issue the JWT.
- Install Metamask add-on for Chrome or Firefox. This add-on “brings Ethereum on your browser”. Practically Metamask provide a web3 object that is used to interact with Ethereum blockchain from your DApp, take care of your private key and manage transactions from within the browser.
- Optional. Run a Geth node. I’ll show you two methods to recover an Ethereum account from the signature, one of them require your API layer to call a JSON RPC against a Geth node.
Note: Infura is not enough for now because they not allow most of web3.personal.* JSON RPC. For development purpose running a Geth node, is straightforward. In a production environment, running a Geth node is not a simple task due to security concerns. The best way to do so is relying on Blockchain as a Service (BaaS) stack provided by AWS or Azure.
- Developement stack: Visual studio 2017 and Node Package Manager (NPM).
- Basic knowledge of Ethereum/Asp.Net Core/Frontend development, basic knowledge of JWT authentication flow.
Let’s do this!
Open Visual Studio 2017, create the EthereumJwtSolution and add two Asp.Net Core 2 Web Application project: EthereumJwtApi and EthereumJwtClient. Choose the empty project scaffolding for both projects.
We need to prepare EthereumJwtApi to create and handle JWT token in order to protect some secure endpoint. The task is straightforward because Asp.Net Core 2 has a built-in JWT mechanism ready to be plugged in our application. Open Startup.cs and modify the ConfigureServices method:
then modify the Configure method:
we are telling our API application to use the JWT Authentication service. In order to work with our client, we need to configure a Cors policy too. We define the JWT configuration settings in appsetting.json:
Create a simple as a possible secured endpoint for testing purpose:
The TokenController.cs will take care for JWT requests and the related token issue:
This is a typical JWT controller, the core methods, Authenticate and Authenticate2 is not yet implemented. Once implemented they will do the same job: recover the Ethereum address from the signature and check if it’s equal to the Ethereum address provided by the client.
The LoginVM represents the user credentials provided by the client and UserVM represents a “server side” signed-in user:
The Authenticate method will take Signature and Message properties as input for the ecRecover function, the Authenticate2 method will take Signature and Hash properties instead. I’ll explain later the difference.
As we said, our DApp is a simple HTML/ES6 client. We’ll build the client on top of Asp.Net Core 2 just to take advantage of IIS Express and Visual Studio IDE. For this reason the Configure method in the Startup.cs class will be:
Run Powershell from the project folder and run these NPM commands:
In order to configure webpack/babel, create webpack.config.js file whit the following configuration:
We have instructed webpack to build the src/main.js file to /www/js/main.build.js.
Install Ethereum specific packages:
Let’s build a very simple HTML page. We need a login button and another button to load some secured data from our API layer:
The DApp logic will reside in the src/main.js file, as we specified in the webpack.config.js file. The src/main.js file will be:
- coinbase and accessToken are globals that will store the user Ethereum account and the JWT token respectively.
- The init function initialize the web3 object from the provider provided by Metamask, then it tries to retrieve the user’s account (the coinbase). This requires you unlock your account signing in Metamask.
- The require function is just a wrapper around the hxr object to make ajax call easily to API layer.
- The load_data_btn click handler makes an ajax call to the API layer secured endpoint. This requires a valid accessToken in order to work, otherwise, the API layer will respond with a 401 HTTP response.
- The login_btn click is a two-step function. First, it asks the user to sign an arbitrary message. Once signed, it sends the account, the signature, the plain message and a prefixed hash to the token endpoint.
Note that web3.personal.sign takes as input the bytes array of the plain string in Hex format (0x…).
As we said, server side, we’ll recover the public key from the signature using two different ways: in one we will use web3.personal.ecrecover JSON RPC (the web3.personal.sign counterpart); in the other one, we will use an underlying ecrecover offline function. According to the documentation, web3.personal.sign use the underlying sign function to sign a hashed and prefixed message, so, in order to use the underlying ecrecover counterpart, we need to compute and send this hash to the Token endpoint too.
Run both the applications and navigate to the client with the browser where Metamask add-on is installed. Remember that in order to build the src/main.js file to js/main.build.js you need to run webpack command from Powershell. If all works, the client will retrieve the coinbase and you’ll see your account displayed on the page:
If you click on the Request Data button now, you’ll get a 401 HTTP response. If you click on the Login button, Metamask will prompt you with a sign request:
Once signed, the handler will make an ajax call to the Token endpoint. At this stage the authenticate methods doesn’t check any signature so the endpoint will always issue the JWT token. Once received the JWT token, the client will be able to call the secured endpoint via ajax. If you click the Request Data button now, you will receive a 200 HTTP response and your data payload:
Retrieve the Ethereum account from a signature
Until now, EthereumJwtApi is a simple JWT Asp.Net core sample because it doesn’t provide any valid authentication method.
The key part of the TokenController are the two Authenticate methods and their abilities to retrieve the Ethereum account from the signature. In order to do so, you need to install Nethereum.Web3 NuGet package. Nethereum is a .Net implementation of Ethereum.
The Authenticate method simply make a JSON RPC call to the web3.personal.ecrecover function:
- web3.personal.sign is the counterpart of the web3.personal.sign so you don’t need to worry about its underlying implementation.
- Requires your own Geth node. Parity is not supported and Infura doesn’t allow web3.personal.* JSON RPC.
Authenticate2 method show an alternative way, it uses an offline implementation of the underlying ecrecover function:
- Doesn’t require JSON RPC call in order to work. MessageSigner.EcRecover is an offline function provided by Nethereum.
- You need to take care of the web3.personal.sign implementation in order to recover the account correctly. For this reason, client side, we have computed the prefixed message hash accordingly.
Now you have the knowledge and a backbone project to secure your Asp.Net Core 2 API with Ethereum. Just a few notes:
- web3 1.0.0 is in beta, web3.personal.sign implementation can vary over time. Be sure to use this kind of authentication method on a codebase you can maintain.
- Maybe Infura can decide to allow web3.personal.ecrecover one of these days ;-)