オンラインユーザーは ますます耐性 従来の電子メール/パスワード登録プロセスに。 Facebook、Google、またはGitHubを介したワンクリックのソーシャルログイン機能が、はるかに望ましい代替手段であることが判明しました。ただし、トレードオフが伴います。
ソーシャルメディアログイン統合の長所:
ソーシャルメディアログイン統合の短所:
この記事では、新しいログイン方法を紹介します ブロックチェーン開発 :ワンクリックで暗号的に安全なログインフロー MetaMask拡張機能 、すべてのデータは独自のバックエンドに保存されます。これを「MetaMaskでログイン」と呼びます。
千の言葉に値する写真です。これから作成するログインフローのデモを次に示します。
いいね?始めましょう!
基本的な考え方は、秘密鍵を使用してデータに署名することで、アカウントの所有権を暗号的に簡単に証明できるということです。バックエンドによって生成された正確なデータに署名することができた場合、バックエンドはあなたをそのパブリックアドレスの所有者と見なします。したがって、ユーザーのパブリックアドレスを識別子として使用して、メッセージ署名ベースの認証メカニズムを構築できます。
はっきりしない場合は、少しずつ説明しますので、大丈夫です。
に接続されているツールを使用している間、注意してください イーサリアム ブロックチェーン(MetaMask、Ethereumパブリックアドレス)の場合、このログインプロセスは実際にはブロックチェーンを必要としません。暗号化機能のみが必要です。そうは言っても、MetaMaskは そのような人気のある拡張機能 、今はこのログインフローを紹介する良い機会のようです。
MetaMaskが何であるかをすでに知っている場合は、このセクションをスキップしてください。
MetaMask はブラウザプラグインであり、 MetaMaskChrome拡張機能 または Firefoxアドオン 。コアでは、イーサリアムウォレットとして機能します。インストールすることで、イーサリアムまたはトークンの送受信を開始できる一意のイーサリアムパブリックアドレスにアクセスできるようになります。
しかし、MetaMaskはイーサリアムウォレット以上のことをします。ブラウザ拡張機能として、閲覧している現在のウェブページとやり取りできます。これは、と呼ばれるJavaScriptライブラリを挿入することによって行われます。 web3.js あなたが訪問するすべてのウェブページで。注入されると、web3
オブジェクトはwindow.web3
を介して利用可能になりますこのウェブサイトのJavaScriptコードで。このオブジェクトがどのように見えるかを確認するには、window.web3
と入力するだけです。 MetaMaskがインストールされている場合は、ChromeまたはFirefoxDevToolsコンソールで。
Web3.jsは、イーサリアムブロックチェーンへのJavaScriptインターフェイスです。次の機能があります。
web3.eth.getBlockNumber
)web3.eth.coinbase
)web3.eth.getBalance
)web3.eth.sendTransaction
)web3.personal.sign
)MetaMaskがインストールされている場合、フロントエンドコードはこれらすべての機能にアクセスできます。 ブロックチェーンと相互作用する 。という dapps または DApps (分散型アプリの場合-スタイルが「ĐApps」の場合もあります)。
DApp開発に関連: タイムロックウォレット:イーサリアムスマートコントラクトの概要web3.jsのほとんどの関数は読み取り関数(ブロックの取得、バランスの取得など)であり、web3
すぐに応答します。ただし、一部の関数(web3.eth.sendTransaction
やweb3.personal.sign
など)では、秘密鍵を使用して一部のデータに署名するために現在のアカウントが必要です。これらの関数は、MetaMaskをトリガーして確認画面を表示し、ユーザーが自分が何に署名しているかを知っていることを再確認します。
これにMetaMaskを使用する方法を見てみましょう。簡単なテストを行うには、DevToolsコンソールに次の行を貼り付けます。
web3.personal.sign(web3.fromUtf8('Hello from ApeeScape!'), web3.eth.coinbase, console.log);
このコマンドの意味:コインベースアカウント(つまり、現在のアカウント)を使用して、utf8からhexに変換されたメッセージに署名し、コールバックとして署名を出力します。 MetaMaskポップアップが表示され、署名すると、署名されたメッセージが印刷されます。
web3.personal.sign
を使用しますログインフローで。
このセクションに関する最後の注意:MetaMaskは現在のブラウザーにweb3.jsを挿入しますが、実際には、web3.jsを挿入する他のスタンドアロンブラウザーもあります。 靄 、 例えば。しかし、私の意見では、MetaMaskは今日、通常のユーザーがdappsを探索するための最高のUXと最も簡単な移行を提供します。
Visual StudioiOSアプリのチュートリアル
から始めましょう どうやって 。ザ・ どうやって 安全だと納得していただけるといいのですが、 なぜ 部分的に短い。
概要で述べたように、ブロックチェーンについては忘れます。従来のWeb2.0クライアントサーバーRESTfulアーキテクチャがあります。フロントエンドWebページにアクセスするすべてのユーザーにMetaMaskがインストールされていることを前提としています。この仮定の下で、パスワードなしの暗号的に安全なログインフローがどのように機能するかを示します。
まず第一に、私たちのUser
モデルには、2つの新しい必須フィールドが必要です:publicAddress
およびnonce
。さらに、publicAddress
一意である必要があります。通常のusername
、email
、およびpassword
を維持できますフィールド(特に、MetaMaskログインを電子メール/パスワードログインと並行して実装する場合)。ただし、これらはオプションです。
publicAddress
のように、サインアッププロセスもわずかに異なります。ユーザーがMetaMaskログインを使用する場合は、サインアップ時に必須フィールドになります。ユーザーがpublicAddress
を入力する必要はありませんのでご安心ください。 web3.eth.coinbase
を介してフェッチできるため、手動で取得できます。
データベース内のユーザーごとに、nonce
でランダムな文字列を生成しますフィールド。たとえば、nonce
大きなランダムな整数にすることができます。
フロントエンドJavaScriptコードでは、MetaMaskが存在すると仮定して、window.web3
にアクセスできます。したがって、web3.eth.coinbase
と呼ぶことができます。現在のMetaMaskアカウントのパブリックアドレスを取得します。
ユーザーがログインボタンをクリックすると、バックエンドに対してAPI呼び出しが発生し、パブリックアドレスに関連付けられているナンスが取得されます。フィルタパラメータを持つルートのようなものGET /api/users?publicAddress=${publicAddress}
する必要があります。もちろん、これは認証されていないAPI呼び出しであるため、このルート上の公開情報(nonce
を含む)のみを表示するようにバックエンドを構成する必要があります。
前のリクエストで結果が返されない場合は、現在のパブリックアドレスがまだ登録されていないことを意味します。最初にPOST /users
を介して新しいアカウントを作成し、publicAddress
を渡す必要があります。リクエスト本文で。一方、結果がある場合は、そのnonce
を保存します。
フロントエンドがnonce
を受信すると前のAPI呼び出しの応答で、次のコードを実行します。
web3.personal.sign(nonce, web3.eth.coinbase, callback);
これにより、MetaMaskは、メッセージに署名するための確認ポップアップを表示するように求められます。ナンスはこのポップアップに表示されるため、ユーザーは自分が悪意のあるデータに署名していないことがわかります。
彼女または彼がそれを受け入れると、コールバック関数は、引数として署名されたメッセージ(signature
と呼ばれる)を使用して呼び出されます。次に、フロントエンドはPOST /api/authentication
に対して別のAPI呼び出しを行い、両方のsignature
を含む本体を渡します。およびpublicAddress
。
バックエンドがPOST /api/authentication
を受信したときリクエストの場合、最初にpublicAddress
に対応するデータベース内のユーザーをフェッチします。リクエスト本文に記載されています。特に、関連するナンスをフェッチします。
ナンス、パブリックアドレス、および署名があれば、バックエンドは次のことができます。 暗号的に検証する ナンスがユーザーによって正しく署名されていること。この場合、ユーザーはパブリックアドレスの所有権を証明しており、認証されていると見なすことができます。その後、JWTまたはセッション識別子をフロントエンドに返すことができます。
ユーザーが同じ署名で再度ログインするのを防ぐために(侵害された場合に備えて)、同じユーザーが次にログインしたいときに、新しいナンスに署名する必要があることを確認します。これは、別のランダムなnonce
を生成することによって実現されます。このユーザーのために、それをデータベースに永続化します。
そして、あなたは行きます! これが、ノンス署名のパスワードなしのログインフローを管理する方法です。
定義上、認証は実際にはアカウントの所有権の証明にすぎません。パブリックアドレスを使用してアカウントを一意に識別する場合、アカウントを所有していることを証明するのは暗号的に簡単です。
ハッカーが特定のメッセージとその署名(実際の秘密鍵ではない)を入手するのを防ぐために、署名するメッセージを次のように強制します。
説明では、ログインが成功するたびに変更しましたが、タイムスタンプベースのメカニズムも想像できます。
このセクションでは、上記の6つの手順を1つずつ説明します。このログインフローを最初から構築する方法、またはあまり労力をかけずに既存のバックエンドに統合する方法のコードスニペットをいくつか示します。
この記事の目的のために、小さなデモアプリを作成しました。私が使用しているスタックは次のとおりです。
私はできるだけ少ないライブラリを使用しようとしています。コードが十分に単純で、他の技術スタックに簡単に移植できることを願っています。
プロジェクト全体はで見ることができます このGitHubリポジトリ 。デモがホストされています ここに 。
2つのフィールドが必要です:publicAddress
およびnonce
。 nonce
を初期化しますランダムな大きな数として。この番号は、ログインが成功するたびに変更する必要があります。オプションのusername
も追加しましたここで、ユーザーが変更できるフィールド。
const User = sequelize.define('User', { nonce: { allowNull: false, type: Sequelize.INTEGER.UNSIGNED, defaultValue: () => Math.floor(Math.random() * 1000000) // Initialize with a random nonce }, publicAddress: { allowNull: false, type: Sequelize.STRING, unique: true, validate: { isLowercase: true } }, username: { type: Sequelize.STRING, unique: true } });
簡単にするために、publicAddress
を設定します小文字のフィールド。より厳密な実装では、検証関数を追加して、ここにあるすべてのアドレスが 有効なイーサリアムアドレス 。
これはdefaultValue()
で行われます上記のモデル定義の関数。
次のステップは、バックエンドにボイラープレートコードを追加して、User
でCRUDメソッドを処理することです。モデル。ここでは行いません。
フロントエンドコードに切り替えると、ユーザーがログインボタンをクリックすると、handleClick
が表示されます。ハンドラーは次のことを行います。
class Login extends Component { handleClick = () => { // --snip-- const publicAddress = web3.eth.coinbase.toLowerCase(); // Check if user with current publicAddress is already present on back end fetch(`${process.env.REACT_APP_BACKEND_URL}/users?publicAddress=${publicAddress}`) .then(response => response.json()) // If yes, retrieve it. If no, create it. .then( users => (users.length ? users[0] : this.handleSignup(publicAddress)) ) // --snip-- }; handleSignup = publicAddress => fetch(`${process.env.REACT_APP_BACKEND_URL}/users`, { body: JSON.stringify({ publicAddress }), headers: { 'Content-Type': 'application/json' }, method: 'POST' }).then(response => response.json()); }
ここでは、web3.eth.coinbase
を使用してMetaMaskアクティブアカウントを取得しています。次に、これがpublicAddress
かどうかを確認しますバックエンドにすでに存在するかどうか。ユーザーがすでに存在する場合はそれを取得するか、存在しない場合はhandleSignup
に新しいアカウントを作成します。方法。
handleClick
で前進しましょう方法。これで、バックエンドによって指定されたユーザーが所有されます(取得されたものであれ、新しく作成されたものであれ)。特に、nonce
がありますおよびpublicAddress
。これで、これに関連付けられた秘密鍵を使用してナンスに署名する準備が整いましたpublicAddress
web3.personal.sign
を使用します。これはhandleSignMessage
で行われます関数。
web3.personal.sign
に注意してください文字列の16進表現を最初の引数として取ります。 web3.fromUtf8
を使用して、UTF-8でエンコードされた文字列を16進形式に変換する必要があります。また、ナンスのみに署名する代わりに、MetaMask確認ポップアップに表示されるため、よりユーザーフレンドリーな文に署名することにしました:I am signing my once-time nonce: ${nonce}
。
class Login extends Component { handleClick = () => { // --snip-- fetch(`${process.env.REACT_APP_BACKEND_URL}/users?publicAddress=${publicAddress}`) .then(response => response.json()) // If yes, retrieve it. If no, create it. .then( users => (users.length ? users[0] : this.handleSignup(publicAddress)) ) // Popup MetaMask confirmation modal to sign message .then(this.handleSignMessage) // Send signature to back end on the /auth route .then(this.handleAuthenticate) // --snip-- }; handleSignMessage = ({ publicAddress, nonce }) => { return new Promise((resolve, reject) => web3.personal.sign( web3.fromUtf8(`I am signing my one-time nonce: ${nonce}`), publicAddress, (err, signature) => { if (err) return reject(err); return resolve({ publicAddress, signature }); } ) ); }; handleAuthenticate = ({ publicAddress, signature }) => fetch(`${process.env.REACT_APP_BACKEND_URL}/auth`, { body: JSON.stringify({ publicAddress, signature }), headers: { 'Content-Type': 'application/json' }, method: 'POST' }).then(response => response.json()); }
ユーザーがメッセージに正常に署名すると、handleAuthenticate
に移動します。方法。 /auth
にリクエストを送信するだけですバックエンドでルーティングし、publicAddress
を送信します同様にsignature
ユーザーが署名したばかりのメッセージの。
これは少し複雑な部分です。バックエンドは/auth
でリクエストを受信しますpublicAddress
を含むルートおよびsignature
であり、これがpublicAddress
かどうかを確認する必要があります正しいnonce
に署名しました。
最初のステップは、データベースから上記のpublicAddress
を持つユーザーを取得することです。 publicAddress
を定義したため、1つしかありません。データベース内の一意のフィールドとして。次に、メッセージを設定しますmsg
手順4のフロントエンドとまったく同じように、このユーザーのナンスを使用して「…に署名しています」と表示されます。
次のブロックは検証そのものです。いくつかの暗号化が関係しています。冒険心があれば、もっと読むことをお勧めします 楕円曲線の署名 。
動作するハッキングされたクレジットカード番号
このブロックを要約すると、msg
が与えられた場合の動作です。 (nonce
を含む)およびsignature
、ecrecover
関数は、msg
の署名に使用されるパブリックアドレスを出力します。 publicAddress
と一致する場合リクエスト本文から、リクエストを行ったユーザーがpublicAddress
の所有権を証明しました。認証済みと見なします。
User.findOne({ where: { publicAddress } }) // --snip-- .then(user => { const msg = `I am signing my one-time nonce: ${user.nonce}`; // We now are in possession of msg, publicAddress and signature. We // can perform an elliptic curve signature verification with ecrecover const msgBuffer = ethUtil.toBuffer(msg); const msgHash = ethUtil.hashPersonalMessage(msgBuffer); const signatureBuffer = ethUtil.toBuffer(signature); const signatureParams = ethUtil.fromRpcSig(signatureBuffer); const publicKey = ethUtil.ecrecover( msgHash, signatureParams.v, signatureParams.r, signatureParams.s ); const addressBuffer = ethUtil.publicToAddress(publicKey); const address = ethUtil.bufferToHex(addressBuffer); // The signature verification is successful if the address found with // ecrecover matches the initial publicAddress if (address.toLowerCase() === publicAddress.toLowerCase()) { return user; } else { return res .status(401) .send({ error: 'Signature verification failed' }); } })
認証が成功すると、バックエンドはJWTを生成し、それをクライアントに送り返します。これは古典的な認証スキームであり、JWTをバックエンドと統合するためのコードです あなたはリポジトリで見つけることができます 。
最後のステップは、セキュリティ上の理由から、ナンスを変更することです。認証が成功した後、次のコードを追加します。
// --snip-- .then(user => { user.nonce = Math.floor(Math.random() * 1000000); return user.save(); }) // --snip--
それほど難しくはありませんでしたね。繰り返しになりますが、アプリ全体がどのように接続されているか(JWT生成、CRUDルート、localStorageなど)を確認したい場合は、お気軽に GitHubリポジトリ 。
ブロックチェーンには欠陥があり、まだ初期段階にある可能性がありますが、このログインフローを既存のWebサイトに実装する方法を十分に強調することはできません。 今日 。このログインフローがメールとパスワードの両方よりも望ましい理由のリストは次のとおりです そして ソーシャルログイン:
もちろん、MetaMaskログインフローは、他の従来のログイン方法と並行して完全に使用できます。各アカウントとそれが保持するパブリックアドレスの間でマッピングを行う必要があります。
ただし、このログインフローはすべての人に適しているわけではありません。
web3
対応のブラウザがないと明らかに機能しません。視聴者が暗号通貨に興味がない場合は、MetaMaskのインストールを検討する可能性もわずかにあります。最近の暗号ブームで、私たちがに向かっていることを願っています Web3.0インターネット 。これまで見てきたように、web3
このログインフローの前提条件です。デスクトップブラウザでは、MetaMaskがそれを注入します。ただし、モバイルブラウザには拡張機能がないため、このログインフローは、モバイルSafari、Chrome、またはFirefoxではそのままでは機能しません。 web3
を挿入するスタンドアロンのモバイルブラウザがいくつかあります。基本的に、MetaMaskはブラウザにラップされています。この記事の執筆時点ではかなり初期段階ですが、興味がある場合は、をご覧ください。 暗号 、 状態 、および Toshi 。 「LoginwithMetaMask」は、これらのモバイルブラウザで動作します。
モバイルアプリに関しては、答えは「はい」です。ログインフローは機能しますが、準備するための多くの基礎があります。基本的に、単純なイーサリアムウォレットを自分で再構築する必要があります。これには、パブリックアドレスの生成、シードワードの回復、安全な秘密鍵の保存、およびweb3.personal.sign
が含まれます。と確認ポップアップ。幸いなことに、あなたを助けるためのライブラリがあります。アプリ自体が秘密鍵を保持しているため、焦点を当てるべき重要な領域は当然のことながらセキュリティです。デスクトップブラウザでは、このタスクをMetaMaskに委任しました。
したがって、簡単な答えはノーであると私は主張します。このログインフローは今日のモバイルでは機能しません。この方向に努力が注がれていますが、今日の簡単な解決策は、モバイルユーザー向けの従来のログイン方法と並行しています。
この記事では、「MetaMaskを使用したログイン」と呼ばれる、サードパーティが関与しない、暗号で保護されたワンクリックのログインフローを紹介しました。バックエンドで生成されたランダムナンスのデジタル署名がアカウントの所有権を証明し、認証を提供する方法を説明しました。また、デスクトップとモバイルの両方で、従来の電子メール/パスワードまたはソーシャルログインと比較したこのログインメカニズムのトレードオフについても調査しました。
このようなログインフローのターゲットオーディエンスは今日でもまだ少ないですが、従来のログインフローと並行して、独自のWebアプリでMetaMaskを使用したログインを提供することにインスピレーションを感じている方もいらっしゃると思います。ぜひお聞かせください。もし、するなら。ご不明な点がございましたら、下のコメント欄にてお気軽にお問い合わせください。
関連: 究極のENSとĐAppチュートリアル