# 使用 MetaMask 登入第三方網站

By [0x0016區塊猿](https://paragraph.com/@0x0016) · 2022-04-03

---

_本文亦發佈在作者網站： 最後更新：2021-04-06_

[https://www.frank.hk/blog/metamask-login/](https://www.frank.hk/blog/metamask-login/)

當我們逐漸邁向 Web3.0 的今天，原本的網站登入需要透過用戶名密碼的方式，現在又多了一個選擇。只要有一個數字貨幣錢包，便可以使用這個錢包作為身分認證的方式，完全不需要記住複雜的用戶名和密碼。

在開始之前，不妨先看看我製作的一個 MetaMask 登入示範： [按此前往](https://demo.frank.hk/demo/metamasklogin)

MetaMask 是一個非常出名的數字貨幣錢包，在 Chrome 上可以安裝它的[官方 Extension](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn)。另外登入頁面需要使用 web3js ， 可以在 [github](https://github.com/ChainSafe/web3.js) 上下載。也可以直接使用 CDN ：

    <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    

整個登入流程，包括幾個基本步驟，但其實並不複雜，可以簡單的用下面的圖示來表示。

flow

登入流程

### (瀏覽器)檢測MetaMask

使用錢包登入的先決條件是用戶必須安裝 [MetaMask Extension](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn)。因此第一步必須檢測瀏覽器是否已經安裝 MetaMask:

        $(function(){
            if (window.web3) {
                // 有安裝 MetaMask
            } else {
                alert( '沒有安裝 MetaMask' );
            }
        });
    

### (瀏覽器)連接錢包

檢測到已經安裝 MetaMask 之後，便可以嘗試連接錢包和獲得錢包地址。這個步驟需要用戶授權。

        window.web3 = new Web3(window.web3.currentProvider);
        ethereum.request({ method: 'eth_requestAccounts' }).then((result) =>               {  
            address = result[0];
            alert('錢包連接成功');
        }).catch((error) => {
            console.log("error",error);
        });
    

透過調用 `ethereum.request({ method: 'eth_requestAccounts' })` , 可以讓 MetaMask 彈出授權連接錢包的畫面，用戶可以在改畫面選擇想綁定的錢包賬戶。

screen 20210406104830 2x

授權連接 MetaMask

當取得用戶授權後，透過回調的 `address = result[0];` 便可以取得錢包地址。

### (伺服器)取得 nonce

取得錢包地址後，便可以繼續開始登入認證。我們要做的是要確定當前的操作者是這個地址的擁有人。

因為只有這個地址的擁有人才擁有錢包對應的私鑰，因此我們可以透過簽名認證的方式，讓用戶對一段信息使用其錢包的私鑰進行簽名，之後在伺服器端，使用錢包的地址（即公鑰）對簽名信息進行驗證。

首先我們需要取得一個 nonce，即「無意義信息」。這個信息必須要由伺服器端產生，並存儲在記憶體中以便下一步驗證時使用。

例子中我們使用 `url: "/api/metamask/nonce/"+address` 這個 API 來取得 nonce。

### (瀏覽器)對 nonce 進行簽名

在取得了 nonce 之後，便需要用戶使用其私鑰對該 nonce 進行簽名認證。 這一步可以透過調用 `window.web3.eth.personal.sign` 進行。其中第一個參數是經過 hex 後的待簽名數據，即 nonce， 第二個參數是錢包地址，回調參數則包括簽名結果和簽名後的 signature。

Hexdata 可以透過 `window.web3.utils.utf8ToHex( originData )` 來取得。

    
    var hexData = window.web3.utils.utf8ToHex(nonce);
    
    window.web3.eth.personal.sign(hexData, address, function(result, signature){
        console.log(result); // 結果
        console.log(signature); // Signature
    }
    

### (伺服器)驗證簽名

取得用戶對 nonce 的簽名後，伺服器就可以透過用戶的公鑰（即錢包地址）， 對簽名進行驗證，看看 signature 和剛才上一步產生的 nonce 是否匹配，若匹配，則認為用戶是該地址的擁有者，可以授權用戶登入系統。

伺服器對簽名信息的驗證，不同的程式語言做法會不同。這裡以 Java 為例：

    public static final String PERSONAL_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n";
    
    public static boolean validate(String signature, String message, String address) {
            String prefix = PERSONAL_MESSAGE_PREFIX + message.length();
            byte[] msgHash = Hash.sha3((prefix + message).getBytes());
            byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
            byte v = signatureBytes[64];
            if (v < 27) {
                v += 27;
            }
            Sign.SignatureData sd = new Sign.SignatureData(
                    v,
                    Arrays.copyOfRange(signatureBytes, 0, 32),
                    Arrays.copyOfRange(signatureBytes, 32, 64));
    
            String addressRecovered = null;
            boolean match = false;
    
            // Iterate for each possible key to recover
            for (int i = 0; i < 4; i++) {
                BigInteger publicKey = Sign.recoverFromSignature(
                        (byte) i,
                        new ECDSASignature(new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
                        msgHash);
    
                if (publicKey != null) {
                    addressRecovered = "0x" + Keys.getAddress(publicKey);
    
                    if (addressRecovered.equals(address)) {
                        match = true;
                        break;
                    }
                }
            }
            return match;
    }
    

簽名驗證的方法會比較繁瑣。以上的示例代碼是參考[這篇文章](https://www.jianshu.com/p/f781cf3e8fc7)的做法，其它的語言可以參考驗證邏輯做相應的處理。要注意 MetaMask 簽名的信息會自動包含一個 Prefix，`\u0019Ethereum Signed Message:\n<length of message>`，這點在驗證時要做同樣的處理。

當伺服器成功驗證瀏覽器傳送過來的地址和 signature 後，便可以認為用戶登入成功，可以進行後續的授權操作。

### 問題

使用數字貨幣錢包登入，因為需要安裝 extension，目前僅能夠在電腦端進行。暫時無法在手機瀏覽器上運行。要在手機上使用，則必須使用 MetaMask 的手機 App 內建的瀏覽器才可。

技術交流，其他諮詢等，請[按此聯絡](https://www.frank.hk/contact)。

---

*Originally published on [0x0016區塊猿](https://paragraph.com/@0x0016/metamask)*
