使用 MetaMask 登入第三方網站
當我們逐漸邁向 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 內建的瀏覽器才可。