Authentication using public-key cryptography with NodeJS — Part 2

Michel Kansou
6 min readJan 6, 2020

--

This is part 2 of 2 part series. You can access Part 1 here.

Before building the experiment what we want is a simple secure way to authenticate a user as we said before in Part 1 “we need to know that the connected user is who she claims to be”.

For this experiment, I get inspired by the WhatsApp web app authentication process using a simple QR code.

To make it works:

First, we need to use a common public-key cryptography algorithm so that we can sign a message and verify the signature I used bitcoinjs-lib because I ❤️ Bitcoin 😁 🚀 but you can use another algorithm, the Bitcoin protocol is based on ECDSA (Elliptic Curve Digital Signature Algorithm)

For more details about bitcoin elliptic curve cryptography please check this great explanation written by Jimmy Song

Second, we need a client (Web app), a separate device that holds the private keys in our case (Mobile app) and a server who’s job is to generate a message for each client and to verify that each one has a valid signature.

  1. Generate a unique identifier for each connected client, a message will then be generated on the server and sent back to the client
  2. The Web App will generate a QR code containing the message
  3. The Mobile app will scan the QR code sign our message and send it back to the server with the digital signature and the public key.
  4. The server will send the signature for each connected client

In order for our client web app and server to send and receive multiple requests through a dedicated channel while keeping track of each client, we need a two-way communication session like WebSockets.

First, we’ll go over some plain socket code, followed by WebSocket code. If you already serve assets with something like Express.js, Hapi… we can jump into the socket code. In this experiment, we’ll use a Node.js WebSocket library called ws.

Socket Server JavaScript Code

To maintain existing instances of connections we can create an object connections where we can add each socket instance to this object.

This script runs a Node.js socket server on port 8080.

Whenever a client connects to this server app (IP_ADDRESS:8080) and sends a string to the server over the open socket, the server will check the message if it’s an id of 64 characters then add it to the socket instance and the connections object. Whenever the client closes the session we remove the instance from the connections object.

Generate message hash

To generate a unique message for each client we can use the device id plus a secret then hash the sum with hash256 from bitcoinjs-lib library.

The hash256 function will hash a message with SHA256 twice SHA256(SHA256(MESSAGE)) we use this as a way to make SHA-256 invulnerable to “length-extension” attack.

The bitcoinjs-lib always uses a Buffer (an array of integer) so each time we need converts the hexadecimal string to a Buffer and vice-versa.

Now that we can generate a message, the server can finally send back to the client a payload object that contains our generated message, device id, and destination (which is IP_ADDRESS/ENDPOINT)

Socket Client JavaScript Code

Here we have our web app socket client script, which can connect to the Node.js socket server above.

Before connecting to the server we need to generate a unique id so we can have multiple devices, for this we can use a js library called uuid that generates an RFC4122 UUIDS.

Generate device id

Also, we can add the current Geolocation of our user to the id and we hash the sum with SHA-256.

Now that we can generate a device id, we can start building our client app! First, we check if we have a device id in local storage if not we generate one.

Second, the client script attempts to connect to localhost:8080. If the connection succeeds, then the client sends a string (device id) to the server over the open socket. The server will respond with a JSON string that contains a message and a destination in the ‘message’ event handler.

Third, we convert the JSON string to an object and check if it contains a message and destination. Finally, we show the content received in a QR code format.

Before jumping to the mobile app we need to create a route to the destination in our case /sign-in.

Routing refers to the mechanism for serving the client the content it has asked for.

For the sign-in, we create an HTTP request with a POST method, we expect the client to send the device id, the message we send it to the client, a signature for the message and the public key.

Server Socket and Sign-in route

We check if the connections object contains the device id then we generate the message again because we know that the correct message signed is just the hash256 of the device id with the secret.

Thanks to bitcoinjs-lib library we have ECPair.fromPublicKey which allows us to verify the message was signed from a public key, the verify function should return a boolean.

If everything is valid we can send a message to the client web app a payload that contains the signature and the public key.

Mobile App Code

For the mobile app, I’ve chosen to make a quick example with React-Native & Expo.

Now in the mobile app using the bitcoinjs-lib library, we can generate a new private key or import an existing one. For, this experiment I imported a private key from a WIF (Wallet import format)

Signing message with private key

Here we have our script, which takes the message as an argument then converts the message to a Buffer and using ECPair.fromPrivateKey we can sign this message.

To scan the QR code, we can use the BarCodeScanner component provided by the Expo framework and all we have to do is to simply parse the JSON string, sign the message with our signMessage function and send a POST request with the destination received in the QR Code.

React Native App

After the request is sent the server will send a message to the client web app with the signature and the public key. The client web app can now ask to the server as an authenticated user by providing each time the device id, the signature and the public key in the header of each request.

Middleware

For the server to verify each request we create a middleware.

Conceptually middleware is a way to encapsulate functionality, in our case functionality that operates on an HTTP request to our application. Practically, middleware is simply a function that takes three arguments:

  • a request object
  • a response object
  • a next function

Here’s an example of how we can verify if each request has a valid signature

Conclusion

This is my first Medium post, I hope I was able to give my idea clear to everyone if you have any question please leave a comment also your feedback is welcome 🙂.

I enjoyed building this experiment, it works well for web apps on a desktop situation but for mobile apps, it won’t work easily we need a simple way for user to send signature to the mobile app like a simple fingerprint, I’m thinking that in the future we can use for example Samsung Blockchain Keystore it stores private keys in a hardware device isolated from the phone OS inside the phone, it can create signatures for each mobile app as a way to authenticate securely while protecting private keys from data breach, malware or other threats.

--

--

Michel Kansou

Full-Stack Engineer. Cryptography. Bitcoin ⚡️ enthusiast