以太坊 - 如何使用PHP的Laravel构建一键登录功能,支持MetaMask - Quicknode

  • QuickNode
  • 发布于 2024-12-06 18:45
  • 阅读 1117

本文教程详细介绍了如何使用 MetaMask 实现基于 Web3 的一键登录,结合 Laravel 和 Vue.js 框架。作者提供了从项目设置到前端登录功能的实现,包括代码示例和必要的依赖安装,最后讲解了如何验证签名以完成用户登录。整个流程清晰且具有实用性,适合对区块链技术和 Web3 应用有兴趣的开发者。

概述

社交登录:我们都见过它们,我们都使用过它们。"用 Facebook 登录"。"用 GitHub 登录"。

如果你在 Web3 社区附近逛过,你可能遇到过一个较新的玩家:"用 MetaMask 登录"。例如,这就是你可能如何注册 OpenSea 的方式,OpenSea 是 Ethereum 和 Polygon 的最大 NFT 市场。

MetaMask 是一个用于以太坊区块链的加密钱包,它还允许你与 dApps(去中心化应用)进行交互。具体来说,它允许 dApps 验证你是某个以太坊地址的所有者,而这将成为你的在线身份。

本教程将向你展示如何使用 web3.js 和 PHP 后端实现一键 MetaMask 登录。虽然我们将在本教程中使用 Laravel 和 Vue,但以下原则:

  • 在 Javascript 中使用 web3.js 签署消息,以及

  • 在 PHP 后端验证签名

……是完全可以转移到你选择的任何 JS 和 PHP 框架的。

前提条件:

为了跟随本教程中的步骤,你需要:

  • 安装 MetaMask 作为浏览器扩展,至少有 1 个账户(无需 ETH!)。如果你还没有安装 MetaMask,可以查看他们的 网站。— 如果你想将你的 QuickNode RPC 配置到 MetaMask,我们也为你提供了帮助。

  • 一个可以运行新 Laravel 安装的本地开发环境。查看优秀的 Laravel 文档 以开始。在本教程中,我将使用 通过 Composer 安装 方法,但如果你更喜欢使用 Laravel Sail(Docker),也没问题。使用 Laravel Sail 时,你需要在所有命令前添加前缀 ./vendor/bin/sail(例如 ./vendor/bin/sail composer install 而不是 composer install);有关如何执行命令的更多信息,请参考 官方 Laravel Sail 文档

  • 对运行终端命令(NPM/Composer 安装)有一般知识和熟悉度。

我们在 这里 也提供了源代码供你查看。

设置项目

好了,让我们开始吧!

首先,我们将创建一个新的 Laravel 项目。如前所述,我将使用 composer create-project 方法。如果你已经在本地计算机上安装了 PHP 和 Composer,这个方法运行得非常好。查看官方 Laravel 文档 以获取更多可用的安装选项。

在你的终端中运行以下命令以生成项目:

composer create-project laravel/laravel metamask-demo-app

安装 Jetstream

为了在前端提前准备好,我们将引入官方 Laravel 包 "Jetstream",它为我们提供了一个漂亮的预制仪表板,其中包括一个登录表单!

在新创建的 metamask-demo-app 文件夹中,你可以运行:

composer require laravel/jetstream

安装完成后,我们将告诉 Jetstream 使用 Inertia(Vue3)预设来构建我们的应用程序。这将包括 Vue 3 和 Tailwind CSS 的堆栈。

php artisan jetstream:install inertia

最后,让我们安装新添加的 NPM 依赖并编译资产:

npm install && npm run dev

启动和运行

为了使新的 Laravel 应用程序启动并运行,我们需要添加数据库连接。通常,这将是 MySQL 或 PostgreSQL 数据库,但出于演示目的,我们可以通过在数据库目录中创建一个名为 database.sqlite 的空文件来使用 SQLite 数据库。

为此,从项目根目录运行以下命令:

touch database/database.sqlite

我们还需要更新我们的 .env 文件以使用 sqlite 连接并注释掉或删除未使用的变量(重要):

DB_CONNECTION=sqlite
##DB_HOST=
##DB_PORT=
##DB_DATABASE=
##DB_USERNAME=
##DB_PASSWORD=

最后,我们可以迁移我们的数据库迁移并将 Laravel 应用程序提供给浏览器!

php artisan migrate
php artisan serve

最后一个命令应该输出如下内容:

Starting Laravel development server: http://127.0.0.1:8000

在浏览器中打开该 URL,你应该会看到默认的 Laravel 欢迎屏幕。

导航到 http://127.0.0.1:8000/login,你应该看到一个 Jetstream 登录表单。

恭喜🎉 — 我们现在准备开始编码了!

准备前端

首先 — 让我们通过添加我们的 "用 MetaMask 登录" 按钮来调整前端。

打开文件 resources/js/Pages/Auth/Login.vue,并在 logo 模板部分之后(大约在第 5 行)添加以下 HTML。

在根 <template> 标签之间,在 <template #logo>... </template> 部分之后

// resources/js/Pages/Auth/Login.vue

<div class="text-center pt-4 pb-8 border-b border-gray-200">
    <jet-button @click="loginWeb3">
        用 MetaMask 登录
    </jet-button>
</div>
<div class="py-6 text-sm text-gray-500 text-center">
    或者用你的凭据登录…
</div>

正如你可能注意到的,按钮已经指定了一个点击处理程序,暂时在现有 submit 方法之后添加一个新的空方法,命名为 loginWeb3:

<script> 标签的底部,添加 async loginWeb3 方法

// resources/js/Pages/Auth/Login.vue
methods: {
    submit() {
        // ...
    },
    async loginWeb3() {
        // 我们的 Meta Mask 集成将在这里进行
    }
}

如果你在终端中运行 npm run dev 并进行编译,结果应该看起来像这样:

创建签名请求

现在按钮没有做任何事情。让我们修复它!

我们将首先安装 NPM 包 web3,我们将需要它:

npm install web3

现在我们准备填充一些逻辑。

首先,在 <script> 部分的顶部添加这些导入:

<script> 部分的顶部

// resources/js/Pages/Auth/Login.vue
import Web3 from 'web3/dist/web3.min.js'
import { useForm } from '@inertiajs/inertia-vue3'

接下来更新空的 loginWeb3 函数,使其看起来如下:

替换之前添加的 async loginWeb3 方法

// resources/js/Pages/Auth/Login.vue
async loginWeb3() {
    if (! window.ethereum) {
        alert('未检测到 MetaMask。请尝试从启用 MetaMask 的浏览器重试。')
    }

    const web3 = new Web3(window.ethereum);

    const message = [
        "我已阅读并接受此应用程序的条款和条件 (https://example.org/tos).",
        "请给我发送登录请求!"
    ].join("\n")

    const address = (await web3.eth.requestAccounts())[0]
    const signature = await web3.eth.personal.sign(message, address)

    return useForm({ message, address, signature }).post('/login-web3')
}

在上面的代码中,我们正在做的事情:

  1. 确保当前浏览器中存在 MetaMask,通过检查 window.ethereum 属性是否存在。否则通知用户。(第 3-5 行)

  2. 准备我们希望用户签署的消息。我们可以把它设置得有点实用,比如让用户接受该应用程序的条款和条件。不过,完全取决于你希望用户签署什么。(第 9-12 行)

  3. 请求用户的账户。这是用户将看到的第一个弹出窗口,他们必须选择哪个地址进行登录。(第 14 行)

  4. 让用户签署我们的消息—在这种情况下接受我们的条款与条件。(第 15 行)

  5. 最后,将消息、地址和签名发送到后端。(第 17 行)

运气好的话,在编译后,你应该能在浏览器中看到以下流程:

快速插曲:

在签名消息方面,我们发现有一个小怪癖需要注意。截止到撰写时, 如果你输入任何长度恰好为 32 个字符的消息,该消息将在 MetaMask 中以 HEX 格式呈现。因此,消息 "Hello world but a lil bit longer" 变成了:

实际上,这并不是什么大问题,但对我来说,这使我花了几个额外小时和一些头发去弄清楚为什么我的消息未以纯文本显示。现在你知道了!

验证签名

我们已经准备好签名,并且将其发送到后端。现在我们需要:

  • 验证签名是否真实

  • 检查地址是否与我们数据库中现有用户匹配,或者创建一个新用户

  • 登录用户并重定向到应用程序仪表板

让我们开始吧。

安装依赖

在我们实际编码部分之前,我们需要安装一些依赖。

composer require kornrunner/keccak --ignore-platform-reqs
composer require simplito/elliptic-php --ignore-platform-reqs

我们添加了 --ignore-platform-reqs 标志,因为 composer 会抛出一个错误,表明缺少所需的 ext-gmp 扩展。

要使此演示正常工作,GMP 扩展不是必需的,我们可以安全地忽略它。

但是,如果你出于某种原因仍希望安装 GMP,你可以找到这个 gist

准备 users

打开 database/migrations/2014_10_12_000000_create_users_table.php 文件,并将 up 方法替换为以下代码:

## database/migrations/2014_10_12_000000_create_users_table.
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('eth_address')->nullable();
        $table->string('name')->nullable();
        $table->string('email')->unique()->nullable();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password')->nullable();
        $table->rememberToken();
        $table->foreignId('current_team_id')->nullable();
        $table->string('profile_photo_path', 2048)->nullable();
        $table->timestamps();
    });
}

我们所做的更改包括:

  • 添加了一个 eth_address 字段,用于存储用户的以太坊地址

  • nameemailemail_verified_atpassword 设置为可为空

记得在之后刷新数据库,使用:

php artisan migrate:refresh

登录逻辑

打开 routes/web.php 文件,并添加以下行:

在文件底部添加—在 Route::middleware(['auth:sanctum', 'verified'])->get(...) 部分之后

## routes/web.
Route::post('login-web3', \App\Actions\LoginUsingWeb3::class);

注意:我们使用 routes/web.php 文件而不是 api.php,因为一旦用户通过身份验证,我们需要访问会话/ cookie 状态以登录用户。

接下来,让我们创建 app/Actions/LoginUsingWeb3.php 文件,它将保存实际的登录逻辑。将以下代码复制/粘贴到其中:

## app/Actions/LoginUsingWeb3.
<?

namespace App\Actions;

use App\Models\User;
use Illuminate\Http\Request;
use Elliptic\EC;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use kornrunner\Keccak;

class LoginUsingWeb3
{
    public function __invoke(Request $request)
    {
        if (! $this->authenticate($request)) {
            throw ValidationException::withMessages([
                'signature' => '无效的签名。'
            ]);
        }

        Auth::login(User::firstOrCreate([
            'eth_address' => $request->address
        ]));

        return Redirect::route('dashboard');
    }

    protected function authenticate(Request $request): bool
    {
        return $this->verifySignature(
            $request->message,
            $request->signature,
            $request->address,
        );
    }

    protected function verifySignature($message, $signature, $address): bool
    {
        $messageLength = strlen($message);
        $hash = Keccak::hash("\x19Ethereum Signed Message:\n{$messageLength}{$message}", 256);
        $sign = [
            "r" => substr($signature, 2, 64),
            "s" => substr($signature, 66, 64)
        ];

        $recId  = ord(hex2bin(substr($signature, 130, 2))) - 27;

        if ($recId != ($recId & 1)) {
            return false;
        }

        $publicKey = (new EC('secp256k1'))->recoverPubKey($hash, $sign, $recId);

        return $this->pubKeyToAddress($publicKey) === Str::lower($address);
    }

    protected function pubKeyToAddress($publicKey): string
    {
        return "0x" . substr(Keccak::hash(substr(hex2bin($publicKey->encode("hex")), 1), 256), 24);
    }
}

这里有一些事情正在进行,让我们逐步分析。

__invoke_ 方法

这是我们注册的路由的入口点,将接收来自前端的 POST 请求。

实际逻辑非常简单,因为我们:

  • 验证从前端发送的签名

  • 根据用户的地址查找或创建新用户。当创建新用户时,我们将确保在专用的 eth_address 字段中存储地址。

  • 登录用户,并重定向到 Jetstream 控制面板。

_verifySignature_ 方法

这是一种标准化的方法,用于通过加密验证以太坊签名是否与相应签署的消息和地址匹配。

它在功能上等同于 web3.eth.personal.ecRecover,返回消息 + 签名的签名地址。

我们不会完全深入探讨这段代码的细节(我不是数学家,你也不需要是),但高层解释是我们正在:

  • 重新构建消息的哈希

  • 从签名和哈希的消息中提取公钥

  • 从公钥中提取地址

  • 检查前端发送的地址是否与签署该消息的地址匹配

而且好了!我们现在有了一个可用的登录系统!

如果你回到浏览器并完成登录流程,你应该会被重定向到仪表板。

额外提示:禁用 Jetstream 注册

如果你实际上打算将 Jetstream 用于你的应用程序,并希望确保用户首次登录时始终使用 MetaMask 注册,你可能希望禁用 Jetstream 附带的默认 /register 路由。

你可以打开 config/fortify.php 并注释掉 "registration feature" 行:

## config/fortify.
'features' => [
    // Features::registration(),
    Features::resetPasswords(),
    [...]
],

你的用户现在将只能使用 MetaMask 登录进行注册。

结论

就是这样,朋友们!

在本教程中,我们使用 Vue、Web3、MetaMask 和 Laravel 构建了一个简单的登录流程。

我希望它传达了如何与钱包集成的基本工作流程,以及如何使用签名在服务器端安全地验证用户身份。

由于 Web3 的去中心化特性,你可能希望支持不止 MetaMask 的更多钱包。例如,添加 WalletConnect 将允许通过扫描 QR 码使用移动钱包登录。

要进一步探索支持更多钱包的内容,你可能希望查看这些资源:

订阅我们的 新闻快讯 以获取更多关于以太坊的文章和指南。如果你有任何反馈,请随时通过 Twitter 与我们联系。你也可以在我们的 Discord 社区服务器上与我们聊天,这里有一些你见过的最酷的开发者 :)

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。