User authentication via Metamask & Portis

  • August 11, 2020
  • Michał Mirończuk

Introduction 💬

Applications that use blockchain wallets often face a problem with user authentication. Instead of using the traditional authentication method where the user has to provide a username and password, the user can use external wallets such as MetaMask or Portis. They provide the low level features of each wallet the ability to sign a specific message that will help us recognize and verify the user. In this post, I will walk you through a simple solution and give you a basic user authentication implementation the above wallets and ethereum cryptographic libraries together with JSON Web Token (JWT). If you are not familiar with JWT, please visit this website.

In general, the whole process consists of 6 steps:

  • Establishing a message on which we will work
  • Signing message by the user (using private key implicitly)
  • Sending signature and user address to the server
  • Veryfing signed message
  • Generating JWT
  • Sending access token to the user

Diagram 📌

As I am an advocate of presenting knowledge in a graphical way, I have prepared the sequence diagram below, which will help us in better understanding of the dataflow between parties.

Return access token
Return access token
Sending wallet address with signature
Sending wallet address with signature
Same message hardcoded on both sides.
Same message hardcoded on both sides.
Signing message by wallet
Signing mes...
Recovering personal signature
Comparing recovered address with wallet address 
Comparing r...
Generating JWT for user
Viewer does not support full SVG 1.1

Implementation 🔥

Because of big interested in NestJS, I have used this framework for backend side. Progressive framework facilities building efficient, scalable Node.js server-side applications and provides many embedded modules like @nestjs/jwt package. For client application I have used React as the most popular solution for building frontend side.

The entire implementation of this demo has been uploaded to the GitHub repository on my profile here.

Client 📍

Major part of client code - signing the transaction is in the LoginComponent in authenticate method

authenticate = async () => {
    const accountAddress = this.props.account;
    const signature = await this.props.web3.currentProvider.send(
        accountAddress, //from which account should be signed. Web3, metamask will sign message by private key inconspicuously.
    const signatureResult = signature.result;
    const requestOptions = {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ accountAddress, "signature":signatureResult }),
    //make request to local server
    return fetch('http://localhost:3001/auth/login', requestOptions)
      .then((response) => response.json())
      .then((token) => {
        this.setState({ access_token: token });

Thanks to extended web3 library we have an access to many functions. To sign a message we have took adventage of personal_sign method where we need to pass two arguments:

  • message - data which we are signing, has to be the same and common for the client app as well as for the server
  • account address - from which account should be signed. Web3 library will sign message by private key implicitly. 💡

Isn't it easy to generate a signature? 🚀🚀🚀

Now we are able to send the signature together with wallet address to our server 😄

Let's move on to the back-end side where the verification operations will be performed on the secure side.

Server 📍

If we look at the diagram, we are reminded that the backend has to recover the address and compare it with the one sent. Therefore, our application service comes into play with the code below.

export class AppService {
  constructor(private readonly jwtService: JwtService) {}

  loginUser(loginDto: LoginDto): string {
    const { accountAddress, signature } = loginDto;
    var recoveredAddr;
    try {
      recoveredAddr = recoverPersonalSignature({
        data: getMessage(),
        sig: signature,

    } catch (err) {
      throw new HttpException('Problem with signature verification.', 403);
    if (recoveredAddr.toLowerCase() !== accountAddress.toLowerCase()) {
      throw new HttpException('Signature is not correct.', 400);
    //save your user here (i.e var user = await this.usersService.createWalletAccountIfNotExist(createUserDto);)
    const payload: JwtUser = {
      account_address: accountAddress,

    const access_token = this.jwtService.sign(payload);
    return access_token;

On input, the service takes the wallet address and signature. To recover the address, the service must use the function called recoverPersonalSignature provided by eth-sig-util library. The function also requires a shared message but this time the signature is passed instead of the address. ✔️

In the next step is to compare the recovered address with the address received from the user. If they are not equal, an exception is thrown. ✔️

Finally JwtService is injected into AppService in order to generate new access token for the user. ✔️

Different approach? 🤔

A slightly more advanced and secure solution may be that the server generates an initial message that will require the user to sign this particular message.