Lesson 13

Authentication

1

Authentication in an Ethereum DApp is usually very different from other authentication systems that you find in traditional web apps.

Since all the information stored on the blockchain is completely public, it would make no sense to store passwords, or even hashes of passwords for our user accounts. Instead, a good strategy is to rely on our user's unique Ethereum address.

The well-known DApp "CryptoKitties" does this in a clever way. In order to sign up, you need to sign a message using one of your MetaMask wallet's addresses – proving that you own the corresponding private key. It will then create your account by storing your given user info and address in their database.

CryptoKitties' login system.


The good thing with this is that signing a message is completely free, so you don't need to worry about gas costs from the user's perspective.

In our DApp however, we are storing the user info on the blockchain (more precisely, in the UserStorage contract), and not in our own private database. Signing a message will therefore not be enough. We will however use the same concept of associating an Ethereum address to a user profile.

Updating our contracts

As you might have noticed from our registration form in the Next.js app, we're collecting some additional information that we're not yet handling in our contracts – the user's first name, last name, bio and their Gravatar email (to display a profile picture).

Let's update our UserStorage contract to take these new fields into account:

contracts/users/UserStorage.sol

To make our DApp work, we also want to be able to find a profile based on a username or an address, so we'll add two more mappings, each pointing to a user ID:

contracts/users/UserStorage.sol

Now that we've updated the profile structure, we also need to update the createUser function that's in UserStorage.sol. Apart from taking into account the new fields, we'll also populate the two new mappings every time a new user is added:

contracts/users/UserStorage.sol

Finally, we'll also update our controller function in UserController.sol. In it, we first do some checks to make sure that neither the user's address or desired username is taken, before we send all the relevant information to the storage function.

contracts/users/UserController.sol

If you try to run truffle test on this, you'll notice that TestUserStorage fails.

We've got more parameters in our "createUser" function now!

Therefore, let's update both our unit and integration tests so that they take these new fields into account.

test/unit/TestUserStorage.sol
test/integration/users.js


There we go!

Re-deploying our contracts

If we had already deployed our contracts to the production environment, the updates made to UserStorage would pose a problem, since redeploying that contract would clear all the data.

In this case though, since we're still working with our local Ethereum instance, we can simply stop the process running ganache-cli with Ctrl + C, and then relaunch it again, to get a brand new blockchain where the contracts have not been deployed yet. Being able to start from a blank state is sometimes very desirable during the development process!

After restarting your ganache-cli instance, you can redeploy your contracts using:

We'll also run npm run dev again so that our contract artifacts get recompiled with the new changes we've made.

The only downside with this technique is that your fake MetaMask funds from earlier will be back to zero (since the blockchain is completely reset). You will therefore have to send some funds to it again as we did in the chapter "Using MetaMask".

Back to zero!

To do this, run truffle console in a new terminal window and run the following commands (replacing YOUR_METAMASK_ADDRESS with your own MetaMask wallet address obviously):

Now your MetaMask wallet should be topped up and ready to go again!

Calling the function with MetaMask

Let's go back to the frontend. We'll first open the file web3/users.js and edit the createUser function so that it receives and sends all the parameters needed to create a new profile, and not just the username.

client/web3/users.js

Next, we add the createUser method in our RegistrationForm. This method will do some lazy checks to make sure we've filled in all the fields, before it calls the createUser function above.

client/components/RegistrationForm.js

With this, you should now be able to fill in the form, make a MetaMask transaction and create your user!


The MetaMask modal should pop up when you press "Create"...



Awesome!

As you can see, there are clear drawbacks to forcing the user to create their profile on the blockchain. Paying $22.73 (although the amount might vary depending on how busy the network is) to create a user is probably not something that many people are willing to do. It's therefore completely understandable why apps like CryptoKitties have taken a more traditional and centralised approach to data storage.

Now that you know how the decentralised version of account creating works, it's up to you to decide exactly what data makes sense to have on the blockchain for your own app! One mix-and-match variant could be to only save the user ID and address on the blockchain, while storing the rest on a good ol' SQL database for example. For the sake of brevity, we won't do that in this course though.

Authenticating the user

So we now have a newly created profile, but from the end-user's perspective, everything still looks the same. The next step in the process it to log in.

But wait...

No need to log in? How is that possible?

It turns out that your MetaMask account is your login. As we saw before, once you've unlocked your MetaMask wallet, any DApp can see what address you're currently using thanks to eth.getAccounts(). If they know your address, they know your account.

This is quite different from how web apps usually authenticate users, but it's actually very effective. Imagine no longer having to remember (or generate) unique passwords for every website anymore. Instead, MetaMask is your one and only login. Pretty cool huh?

Now that we know this, let's edit our code so that, as soon as the DApp launches, it checks whether the user's address is connected to any existing user ID in the UserStorage contract.

client/web3/users.js

Since we want to display the logged-in user's info in the header, we'll do the check in the Header component.

client/components/Header.js

Refresh the page and you should see your user ID immediately being logged in the JavaScript console (provided that your MetaMask account is unlocked).

Success!


Displaying the user info

Finally, we want to display the name and avatar for the logged-in user in our header. So using this ID that we have, we want to fetch the rest of the user's information.

We can do this pretty easily simply by changing our existing getUserInfo function in web3/users.js so that it returns all of the fields that we have. The only thing to remember is that we want the info to have some user-friendly formatting, which means converting hex strings to normal strings and BigNumber to integers.

client/web3/users.js

To make sure it works, we can import the function into our header and log the full user info instead of just the ID:

client/components/Header.js


There it is!


Alright, we have the info – now we need to display it. We'll start by adding two state variables – loggedIn and userInfo. If the loggedIn variable is true, we'll render a new Nav component with the user's first name and last name.

client/components/Nav.js
client/components/Header.js

As you can see in the Nav component, we've added a link to a profile page with the username as a query parameter. This page obviously doesn't exist yet, but we'll create it in an upcoming chapter.

If you refresh, you should now see your name displayed in the header.


Showing an avatar

To top it off, we also want to render the user's profile picture based on the gravatarEmail that they provided (if no email was provided, we'll just render a default picture).

To make this easy, we install the NPM package gravatar-url.

We then create a new Avatar component where we simply render the Gravatar image based on an email address.

client/components/Avatar.js

Finally we import the Avatar component into our Nav and render it next to the user's name:

client/components/Nav.js


And we're done!


You should now have an idea of how to perform authentication checks in Ethereum. The key part to understand that's fundamentally different from traditional web apps, is that your wallet's address is essentially your user account.

In the next chapter, we'll look at how we can let our logged-in user create new tweets through the DApp's interface.

Comments

Phaedrus Raznikov

@Marc-Antoine Rocca-Serra Beaudet: There's an extra <nav> element wrapping the <Nav /> component we built. If you remove the wrapping element where you render the <Nav /> component so it reads like below, it will work:


Marc-Antoine Rocca-Serra Beaudet

Hello Tristan, When I do this, the logo and user's infos are not horizontally aligned.User's infos are a bit lower, making the header take more place. Do you have any idea why?

Tristan Edwards

@marc-antoine-rocca-serra-beau: It could be many things! :-) If you share your repo with me I could have a look at what the problem might be.