Creating "dead simple" authentication

I am in the process of pivoting my app, Zitrone, to appeal to a wider audience. This change has presented a new challenge in terms of user authentication. In the past, Zitrone was geared towards the crypto community and relied on Ethereum wallets for authentication. However, as I expand the app's reach to a more general audience, I need to find a way to authenticate users who do not have an Ethereum wallet.

Initially, I was unsure how to solve this problem. The first version of the app, called "Dude, RSVP," had no authentication at all. My hope was that users would be honest and not impersonate each other, but I soon realized that this approach had its limitations. There were issues with fake events, and it became clear that a verification system similar to Twitter's blue checkmark would be necessary to ensure the integrity of the app.

This is how it ended
This is how it ended

I considered using social logins and Ethereum wallets as login options, but ultimately decided against it. My main goal was to create a secure yet simple solution for users, and both options were too complex to implement. I also wanted to avoid storing any sensitive or personal information.

Instead, I developed a system that is "dead simple" to use, but less secure than other options like WebAuthn. The registration process is simple for users, they only need to press a button to register.

Click on the button and you are done
Click on the button and you are done

Let’s dig deeper on the registration:

Yes, this is simplified
Yes, this is simplified

The process of registering a user's nickname on a blockchain using the EXM state management solution is triggered by the user pushing a button. The system first checks if the desired nickname is available. If it is, the registration process begins. This process takes place entirely on the user's client computer, with no sensitive information being stored or processed on the server side.

The user's browser generates a set of exportable keys (private and public) using the standard subtle crypto library. Next, a unique ID is generated, and a signature is created using both the user ID and nickname. The keys are then sent to the user interface, and can be used for account recovery. However, this also represents the first security weakness, as anyone who obtains the file would be able to restore the account on any computer.

In an additional step, invisible to the user, the keys are imported as non-exportable and stored in the browser's IndexedDB, along with the signature, nickname, and user ID. A public key with the same information is also saved on the blockchain. This additional information is not strictly necessary for validation, but it is useful for debugging issues and does not harm privacy or security. This concludes the registration process in a nutshell.

That's much simpler huh?
That's much simpler huh?

The authentication process is relatively simple. When the application loads, it checks for locally stored keys. If there are no keys in the local IndexedDB, the process is stopped and an empty object is returned. However, if keys are found, they are loaded along with the signature and user information. The app then seeks the blockchain for a unique ID of the user, and if it is found, it fetches the public key. This public key is used to validate the local signature and the nickname is stored in the global state.

It should be noted that there are two potential attack vectors in this process. The first is the backup key file, which is only as secure as the client computer. To mitigate this risk, users can be instructed to upload the key to an external device, such as a USB key, which would provide airgap security. The second potential attack vector is the fact that the private key is stored in the IndexedDB. While it is stored securely and cannot be exported or read by the user, it could potentially be used in a cross-site scripting (XSS) attack. To minimize this risk, it is important to understand how XSS works and take steps to mitigate the risk as much as possible. This is true for any authentication or framework, as XSS is often the biggest security threat.

The system described in the article is inspired by three different technologies. The first is WebAuthn, which is a more secure and advanced way to authenticate users than the "dead simple" method described in the article. However, it is also more complicated to use and relies on biometric features of the client device. It could be a better solution for a larger scale project such as a social network or a banking system. The second technology is Arweave’s login process which uses a file-based token as a security mechanism. It is a secure option but it is less convenient for users. Lastly, the system is inspired by CommonGround authentication which creates a hidden wallet for the account and uses a recovery phrase as a means to authenticate the account on different computers.

I am currently working on completing the recovery aspect of the flow which will essentially restore the local IndexedDB from the file. Afterwards, I plan to turn this approach into a reusable library. It is likely that I will move the library to React/Node.js as they are more widely used. I also intend to make the storage layer more flexible, allowing for the use of any database or blockchain solution. If you're interested in joining me in this endeavor, please do not hesitate to reach out.

Subscribe to Adam Sobotka
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.