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 21 - Class

>>:  EP 07 - [TDD] 取得 Nonce (2/2)

事件回力镖 - 捕获与冒泡

传递机制听起来非常没有画面感,於是擅自替传递机制取了绰号叫回力镖,如同回力镖有去程,回程以及猎捕到猎...

Day 30 - 後记

经过了一个月的尝试,我们大致上已经掌握了能够自己实现非常基础的 Ruby VM 的能力。虽然在铁人赛...

[Day 29] 资讯系统营运

从前几章IT治理到管理,产出各种规章,执行部门必须依据这些规章完成相应资安防护,以达到提升安全及符合...

图的连通 (6)

9.3 三连通元件 3连通跟2连通真的不太一样。 以2连通来说,如果我们今天把整张图,依照关节点切开...

Day 09 - Spring Boot 常用注释(下)

Spring Boot 的注释是用来告知Spring 框架,底下的程序码代表的意思,并且可以设定相关...