It’s no secret that the Ethereum login will soon become a user standard and passwords will no longer be needed. Nevertheless, dApp development is still a fairly young direction and many standards for their development are still set.
Now all developers continue to write dApps with old practices, instinctively using the same JWT for authentication. I propose a slightly different approach.
I myself started developing dApps using JWT. From the first project, I felt that authentication always becomes tricky and that there must be something redundant in the process. After a couple of projects, I realized that the JWT itself is redundant. Let me explain why.
This diagram shows how I did authentication on my first few projects. Here the scheme almost wholly repeats the standard procedure with JWT, the only thing is that instead of a login and password, the user sends a signature.
Why do we need to get JWT? After all, even without it, you can reliably identify the user by taking the address from his signature.
The user still generates a signature, but already with an Expire-date inside, so that if an attacker gets the signature, it won’t be useful for long (the same as with the JWT). Further, the signature is placed in the standard Authorization header and processed on the server by taking the user’s address and finding the user in the database. That’s all. And you do not need to constantly update the encryption keys for the JWT on the server, so in general, a lot of responsibility falls off the server.
To simplify this flow even more, I made the web3-token module. To install it, use the command:
$ npm i web3-token
This module can be used both on the server and on the client.
Let’s look at an example, starting with the client-side.
import Web3Token from 'web3-token';
// Connection to MetaMask wallet (you can actually use any wallet)
// you can even use ethersjs instead of web3
const web3 = new Web3(ethereum);
await ethereum.enable();
// getting address from which we will sign message
const address = (await web3.eth.getAccounts())[0];
// generating a token with 1 day of expiration time
const token = await Web3Token.sign(msg => web3.eth.personal.sign(msg, address), '1d');
// attaching token to axios authorization header
axios.post('/registration', { name: 'Adam' }, {
headers: {
'Authorization': token,
}
})
// checking how it finds me in backend's database
axios.get('/me', {
headers: {
'Authorization': token,
}
})
After calling the .sign method, you will see something similar to this (if you are using MetaMask).
As you can see, the message is completely transparent for the user since they must see what they are signing. So instead of using JSON structure for better readability, I decided to use the same structure as for HTTP headers.
In the body of the message, we see the version of the token and the expire date itself.
Next, here’s what the backend (Node.js) does with this token:
const Web3Token = require('web3-token');
// getting a token from authorization header
const token = req.headers['Authorization']
const { address, body } = await Web3Token.verify(token);
// now you can find that user by his address
// tip: better to do it case insensitive
req.user = await User.findOne({ address });
It’s pretty simple, just one line, and the module takes over all cryptography. We magically obtain the user’s address from the signature and find them in the database using this address. Then , for example , you may grant this user an NFT by his address.
The result is a very convenient stateless user authentication method, ideal for hybrid dApps. The only drawback is that it is hard to test in Postman 😀
I would really like something like a standard to come out of this, but until then, I am open to criticism (or possibly questions/suggestions)
Web3 is just around the corner.
Top comments (0)