/ethereum-on-rails

ethereum on rails (template): connect metamask to ruby on rails.

Primary LanguageRubyApache License 2.0Apache-2.0

README

Ethereum on Rails MVP: allow sign-up and log-in with Ethereum wallet (e.g., MetaMask browser extension).

volkswagen status Visitors Top Language Open-Source License

✔️ If you are here to learn Ethereum-account authentication, welcome! Feel free to read the following article that is based on this code:

⚠️ If you are here to build Sign-in with Ethereum for production, you should use the SIWE libraries instead:

Screenshot of the log-in page

Required versions

  • Ruby ^3.0.0
  • Rails ^7.0.0
  • Node ^17.4.0

System dependencies

  • 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 rails

Run dev server

nvm use stable
bundle install
bin/rails webpacker:install
bin/rails db:migrate
bin/rails server

How it works

Model: User

The 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 UUID that has to be signed for authentiation.

In this MVP, all three fields are mandatory and have to be unique.

Controller: Users{#new,#create}

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_address from the sign-up view (see below).
  • It ensures the eth_address is a valid Ethereum address.
  • It creates a new user and saves it to the database with the given attributes.
View: Users#new

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 Connect button 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_address to the form and submits it.
Controller: Sessions{#new,#create,#destroy}

The sessions controller manages the user authentication (login/logout).

  • It finds the user by eth_address provided 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.
API: /api/v1/users{#index,#show}

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, returns null

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_account parameter is a valid Ethereum address to filter out seemingly random requests.
  • It finds a user in database by eth_account key.
  • It returns only the eth_nonce as JSON.
  • It returns null if it fails in any step above.

Credits

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.