iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

All In One NFT Website Development系列 第 10

Day 10【連動 MetaMask - Login Flow & Extension Check】The strongest password ever.

【前言】
終於要進到後端的部分啦!一樣先來看 Project 分析,這幾天的內容會環繞在第一步**「連動 MetaMask 系統」和第二步「並且得到當前登入者的以太坊地址」**,我研究了很久才終於理解到底該怎麼實作,希望大家可以留下來品味一下!

【(使用者看見的)前端與(後方運作的)後端】

  1. 使用者會看見登入錢包按鈕(按鈕應該要有一些特效)
    • 按下按鈕後要連到 MetaMask 系統
  2. 使用者點擊按鈕後出現 Loading 特效,同時跳出 Metamask 登入及連動同意
    • 同意後,要從 MetaMask 得到當前登入者的以太坊地址
  3. 使用者同意後出現登入介面,讓其輸入欲登入的角色編號
    • 得到欲登入的角色編號後,去後方資料庫查詢此地址是否真的擁有此 NFT
      // 資料庫建構的部分由其他夥伴負責,這邊我負責檢查 Tokens 的持有地址
  4. 成功登入後的畫面
    // 網頁互動的部分由其他夥伴負責,這邊我負責顯示登入成功 or 失敗

這篇文章的內容我參考來自 MetaMask Docs 的 Ethereum Provider API 以及 Amaury Martiny 的 One-click Login with Blockchain: A MetaMask Tutorial,在文章最下面的參考資料區我都會附上連結。真的非常感謝這些區塊鏈的工程師無私分享自己的智慧結晶!

【Ethereum Provider API】
因為 MetaMask 是一個瀏覽器插件,所以除了可以被當作以太坊錢包使用外,也可以在網頁上藉由每個帳戶擁有獨一無二的公鑰來交易或驗證,以及將 ****MetaMask ****當作登入介面使用。

【Login Flow】
在一個登入授權中,我們主要確認使用者的 publicAddress 確實為其所有,並且確保 nonce 的可行性。此外使用者不需要自己輸入 publicAddress 因為在登入 MetaMask 之後,我們能透過 web3.eth.coinbase 或相關函式來獲取這項資訊。

  1. 在使用者點下登入按鈕後,檢測使用者是否已經下載 MetaMask Chrome extension
    • 還沒下載:將按鈕改為 Click here to install MetaMask,按下後跳出網頁轉往下載頁面。
    • 已經下載:進入 Login Flow 的下一步。
  2. 產出一個亂數的 nonce

    在資訊安全中,Nonce 是一個在加密通信只能使用一次的數字。在認證協定中,它往往是一個隨機或偽隨機數,以避免重送攻擊。Nonce 也用於串流加密法以確保安全。如果需要使用相同的金鑰加密一個以上的訊息,就需要 Nonce 來確保不同的訊息與該金鑰加密的金鑰流不同。 *節錄自維基百科《Nonce》- https://zh.wikipedia.org/zh-tw/Nonc*e

  3. 確認此使用者過去是否登入過,藉由該使用者的 publicAddress 來確認其 nonce
    • 未登入過:創建一個新帳戶資訊在後端,並且初始化。進入 Login Flow 的下一步。
    • 登入過:儲存其 nonce。進入 Login Flow 的下一步。
  4. 當前端接收到了 nonce 需要跳出 MetaMask 的登入介面。呈現 Sign-In Message 並且在使用者登入時(輸入密碼時)呈現 Loading 圖示。
  5. 在使用者登入之後,會回傳authentication,其為一個包含 signaturepublicAddress 的物件。
  6. 當後端接收到了 authentication ****後會進行數位加密簽證,以確保這個 nonce 是被這個使用者所登入。以此就可以確保 publicAddress 與擁有者的所有權。
  7. 為了確保不被同樣的簽名重新登入,最後會重新產生新的 nonce
  8. 成功登入!並隨時偵測使用者是否登出,以及監測使用者是否更換帳號。

以上登入流的想法來自於以下這張圖,出處為 Amaury Martiny 的 One-click Login with Blockchain: A MetaMask Tutorial。

【MetaMask Extension Check】
今天的內容我想著重在 Login Flow 中的第一步**「檢測使用者是否已經下載 MetaMask Chrome extension」**

首先會使用到 @metamask/onboarding 這個套件,其功能為在使用者跳出頁面下載 MetaMask 的onboarding 結束後,redirect 回到原先頁面。

npm install @metamask/onboarding

import MetaMaskOnboarding from '@metamask/onboarding'

GitHub - MetaMask/metamask-onboarding: A library to help onboard new MetaMask users

準備在 Button 裡面呈現的文字以及 styled-components 的資訊。

const ONBOARD_TEXT = 'Click here to install MetaMask!';
// 當 USER 未下載 **MetaMask Chrome extension**
const CONNECT_TEXT = 'Welcome Back to Dino Club!';
// 當 USER 已下載 **MetaMask Chrome extension**
const CONNECTED_TEXT = 'Connected';
// 當 USER 已經連接到 Ethereum 帳戶

// styled-components
const clrneon = "#36D7B7";
const StyledButton = styled.button`
	...
`;

初始化以及 useState

export function OnboardingButton() {

  const [buttonText, setButtonText] = useState(ONBOARD_TEXT);
	// 按鈕要呈現的文字
  const [isDisabled, setDisabled] = useState(false);
	// 跳出下載頁面的時候要使按鈕無效
  const [accounts, setAccounts] = useState([]);
	// 之後要用來記錄 使用者的 ethereum 帳號
  const onboarding = React.useRef();
  ...
  return (
    <StyledButton disabled={isDisabled} onClick={onClick}>
      {buttonText}
    </StyledButton>
  );
}

使用 useEffect 設定 new MetaMaskOnboarding() 跳出 MetaMask Chrome extension 的下載頁面。

export function OnboardingButton() {
  ...
  useEffect(() => {
    if (!onboarding.current) {
      onboarding.current = new MetaMaskOnboarding();
    }
  }, []);
  ...
  return (
    ...
  );
}

使用 useEffect 根據當前是否下載的狀況來改變按鈕功能以及呈現的文字。

export function OnboardingButton() {
  ...
  useEffect(() => {
    if (MetaMaskOnboarding.isMetaMaskInstalled()) {
      if (accounts.length > 0) {
			// 在登入之前 acoount 都不會紀錄任何資料,所以長度會是 0
        setButtonText(CONNECTED_TEXT);
        setDisabled(true);
        onboarding.current.stopOnboarding();
      } else {
        setButtonText(CONNECT_TEXT);
        setDisabled(false);
      }
    }
  }, [accounts]);
  ...
  return (
    ...
  );
}

【小結】
到這裡第一步就大功告成啦!這個部份我用了好幾十個小時才成功。不過好在 MetaMask 的 Doc 有提供非常多資源可以使用,所以才能如願做出來。比較不能完全理解的部分在尋找 window 裡面有 ethereum 的元素部件。我會再研究一陣子等有結果再來跟大家分享!

const isMetaMaskInstalled = () => {
  //Have to check the ethereum binding on the window object to see if it's installed
  const { ethereum } = window;
  return Boolean(ethereum && ethereum.isMetaMask);
};

【參考資料】
Introduction | MetaMask Docs
Breaking Change: No Longer Injecting Web3
One-click Login with Blockchain: A MetaMask Tutorial


上一篇
Day 9【錢包登入區 - Login Interface】你也想起舞嗎?
下一篇
Day 11【連動 MetaMask - Pop Up & Login Detection】Can`t use current password.
系列文
All In One NFT Website Development30

尚未有邦友留言

立即登入留言