Ethereum on Rails MVP: allow sign-up and log-in with Ethereum wallet (e.g., MetaMask browser extension).
✔️ If you are here to learn Ethereum-account authentication, welcome! Feel free to read the following article that is based on this code:
- Ruby
^3.0.0 - Rails
^7.0.0 - Node
^17.4.0
- Ruby, Gems, Rails, SQLite3, Bundler, NodeJS, NPM, Yarn
pacman -S ruby rubygems sqlite nvm
nvm install stable
npm install --global npm yarn
gem install bundler railsnvm use stable
bundle install
bin/rails webpacker:install
bin/rails db:migrate
bin/rails serverThe user contains three attributes: username, eth_address, eth_nonce.
- The Name provides a pretty identity.
- The Address is the unique identifier used for authentiation.
- The Nonce is a random
UUIDthat has to be signed for authentiation.
In this MVP, all three fields are mandatory and have to be unique.
The users controller is solely used for creating new users.
- It generates an initial random nonce with
SecureRandom.uuid. - It ensures the user picks a name.
- It takes the
eth_addressfrom the sign-up view (see below). - It ensures the
eth_addressis a valid Ethereum address. - It creates a new user and saves it to the database with the given attributes.
The Users#new view is the sign-up page to create a new account.
- It contains a field for the user to choose a name.
- It contains a read-only, hidden field that will be populated with the user's Ethereum address.
- It contains a
Connectbutton to establish a connection with the Ethereum provider.
The JavaScript pack users_new.js contains the frontend logic to establish a connection with an Ethereum wallet.
- It hides read-only fields.
- It ensures an Ethereum context is available.
- It adds an click-event listener to the connect button.
- It requests accounts from the available Ethereum wallet:
method: 'eth_requestAccounts' - It adds the
eth_addressto the form and submits it.
The sessions controller manages the user authentication (login/logout).
- It finds the user by
eth_addressprovided by Ethereum wallet. - It ensures user exists in database.
- It ensures user signed a message to authenticate.
- It ensures the signature is not expired (older than 5 minutes).
- It ensures the signed nonce matches with our database.
- It recovers the pubkey and address from the signature.
- It ensures the recovered address matches the address in the database.
- It logs the user in if all the above is true.
- If generates a new nonce for future logins if all of the above is true.
It also handles destroying sessions to log users out.
The JavaScript pack sessions_new.js contains the frontend logic to authenticate a user with an Ethereum account.
- It hides all the read-only fields.
- It ensures an Ethereum context is available.
- It adds a click-event listener to the connect button.
- It requests accounts from the available Ethereum wallet:
method: 'eth_requestAccounts' - It requests the nonce belonging to the account from the API/v1 (see below):
fetch("/api/v1/users/" + account) - It generates a message containing the site's title, the request time, and the nonce from the API.
- It requests the user to sign the message:
method: 'personal_sign', params: [ message, account ] - It populates the form with address, message, and signature and submits it.
To prevent signature spoofing, the user needs to sign a specific piece of information we can verify in the backend rather than a random message. The User model contains an eth_nonce field that gets filled with a random UUID on first sign-up and gets rotated on every successful login.
The #index controller explicitly returns nil to prevent accessing the full set of users from the database.
- GET
/api/v1/users, returnsnull
The #show controller gets a user by eth_address from the database and returns the eth_nonce or nil if it does not exist.
- GET
/api/v1/users/${eth_account} - It ensures the
eth_accountparameter is a valid Ethereum address to filter out seemingly random requests. - It finds a user in database by
eth_accountkey. - It returns only the
eth_nonceas JSON. - It returns
nullif it fails in any step above.
The Ethereum-on-Rails template was written by @q9f can be found and used on Github directly: github/q9f/ethereum-on-rails
This Rails application template implements the logic described by Amaury Martiny in One-click Login with Blockchain: A MetaMask Tutorial - brilliant, though slightly outdated, resource! Thanks for that.
