使用 MetaMask 登入第三方網站

  • FrankFrank
  • /
  • 10 分鐘閱讀
  • /
  • Apr 6, 2021
  • /
  • - views

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

在開始之前,不妨先看看我製作的一個 MetaMask 登入示範: 按此前往

MetaMask 是一個非常出名的數字貨幣錢包,在 Chrome 上可以安裝它的官方 Extension。另外登入頁面需要使用 web3js , 可以在 github 上下載。也可以直接使用 CDN :

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

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

(瀏覽器)檢測MetaMask

使用錢包登入的先決條件是用戶必須安裝 MetaMask Extension。因此第一步必須檢測瀏覽器是否已經安裝 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 彈出授權連接錢包的畫面,用戶可以在改畫面選擇想綁定的錢包賬戶。

當取得用戶授權後,透過回調的 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;
}

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

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

問題

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