<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Metamask - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Metamask - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 24 Jun 2026 22:47:33 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/metamask/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Add a MetaMask Login to Your Laravel Web Application ]]>
                </title>
                <description>
                    <![CDATA[ By Darren Chowles Logging into a website via third parties is ubiquitous online. Almost every other member-based website allows you to log in with accounts like Facebook, Twitter, and Google. If you’ve ever visited NFT marketplaces like OpenSea or Ra... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/add-a-metamask-login-to-your-laravel-app/</link>
                <guid isPermaLink="false">66d45e0055db48792eed3f57</guid>
                
                    <category>
                        <![CDATA[ Laravel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Metamask ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PHP ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 23 Mar 2022 18:38:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/photo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Darren Chowles</p>
<p>Logging into a website via third parties is ubiquitous online. Almost every other member-based website allows you to log in with accounts like Facebook, Twitter, and Google.</p>
<p>If you’ve ever visited NFT marketplaces like OpenSea or Rarible, you would have noticed they allow you to sign in with a crypto wallet like MetaMask. </p>
<p>This login process affirms you’re the owner of the Ethereum address in question and allows the system to authenticate your access. Very similar to how a username and password would allow you access to a gated part of a website.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting this tutorial, I’ll assume you have a basic understanding of Laravel, and that you can initialise a new project in your environment. Even though this tutorial is Laravel-focused, with some tweaking you can apply this to any other PHP project. The concepts remain the same.</p>
<p>I’ve tried to keep this as generic as possible. I only focus on the MetaMask signing and validation, without restricting you to using it with specific front-end technologies (like React or Vue) or authentication scaffolding (like Breeze or Jetstream). This gives you the freedom to implement it with minimal effort into an existing project.</p>
<p>We’ll need the following before we start:</p>
<ul>
<li>A new or existing Laravel project.</li>
<li><a target="_blank" href="https://metamask.io/">MetaMask</a> installed in your browser.</li>
</ul>
<h2 id="heading-boilerplate">Boilerplate</h2>
<p>We’ll start out with some boilerplate code by importing <a target="_blank" href="https://getbootstrap.com/">Bootstrap 5</a> and creating a simple “Log in with MetaMask” button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/image-1-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>MetaMask Login<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-12 text-center"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary mt-5"</span>&gt;</span>Log in with MetaMask<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Easy enough. 😀</p>
<p>We’re also importing the <a target="_blank" href="https://docs.ethers.io/">ethers.js library</a> that will allow us to interact with the Ethereum blockchain via MetaMask, which in this case acts as the interface to the provider (<a target="_blank" href="https://infura.io/">Infura</a> by default).</p>
<h3 id="heading-quick-tip">Quick tip:</h3>
<p>Providers allow us to interact with the Ethereum blockchain. To connect to the network, you need access to a node. Depending on the type of node, it could require a large amount of disk space and bandwidth. Running a node can also be a complex process, especially if you want to focus on development rather than maintaining and operating a node. </p>
<p>Enter, the provider! Companies like Infura provide these nodes as a service, so you don’t need to worry about running your own. Instead, you can access this functionality via their APIs.</p>
<p>You may run into older tutorials that state MetaMask injects web3.js (a library providing similar functionality to ethers.js) into the page by default. This is <a target="_blank" href="https://docs.metamask.io/guide/provider-migration.html#summary-of-breaking-changes">no longer the case</a>.</p>
<h2 id="heading-detect-the-provider">Detect the Provider</h2>
<p>We’ll start off our new <code>web3Login()</code> function by checking that the browser has a provider available. This would be the case if you have MetaMask installed. You can also test this code where MetaMask is not installed (for example, an incognito window) to confirm the detection works.</p>
<p>Add the click event to the button:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary mt-5"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"web3Login();"</span>&gt;</span>Log in with MetaMask<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>And start off the function with our detection snippet in our <code>&lt;head&gt;</code> below the ethers.js import:</p>
<pre><code class="lang-js">&lt;script&gt;
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">web3Login</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">window</span>.ethereum) {
            alert(<span class="hljs-string">'MetaMask not detected. Please install MetaMask first.'</span>);
            <span class="hljs-keyword">return</span>;
        }
    }
&lt;/script&gt;
</code></pre>
<p>Go ahead and test this in a browser with no MetaMask installed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/image-2-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-install-laravel-dependencies">Install Laravel Dependencies</h2>
<p>Before we continue with the front-end login process, we need to put some endpoints in place. Our login script will need these so the user can sign a message with their wallet, and our system can then verify their signature.</p>
<p>We need to install two dependencies via Composer to help us perform hashing and use elliptic curve cryptography:</p>
<pre><code class="lang-bash">composer require kornrunner/keccak
composer require simplito/elliptic-php
</code></pre>
<h2 id="heading-add-laravel-routes">Add Laravel Routes</h2>
<p>Open your <strong>routes/web.php</strong> file and add the following routes:</p>
<pre><code class="lang-php">Route::get(<span class="hljs-string">'/web3-login-message'</span>, <span class="hljs-string">'Web3LoginController@message'</span>);
Route::post(<span class="hljs-string">'/web3-login-verify'</span>, <span class="hljs-string">'Web3LoginController@verify'</span>);
</code></pre>
<p>The first route will return the message that needs to be signed, and the second route will verify the signed message.</p>
<h2 id="heading-create-the-login-controller">Create the Login Controller</h2>
<p>Now it’s time to create the controller that will generate the message and perform the verification.</p>
<p>Create a new file called <strong>Web3LoginController.php</strong> in <strong>app/Http/Controllers</strong> and add the following code to it:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Elliptic</span>\<span class="hljs-title">EC</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Str</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">kornrunner</span>\<span class="hljs-title">Keccak</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Web3LoginController</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span>
    </span>{
        $nonce = Str::random();
        $message = <span class="hljs-string">"Sign this message to confirm you own this wallet address. This action will not cost any gas fees.\n\nNonce: "</span> . $nonce;

        session()-&gt;put(<span class="hljs-string">'sign_message'</span>, $message);

        <span class="hljs-keyword">return</span> $message;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verify</span>(<span class="hljs-params">Request $request</span>): <span class="hljs-title">string</span>
    </span>{
        $result = <span class="hljs-keyword">$this</span>-&gt;verifySignature(session()-&gt;pull(<span class="hljs-string">'sign_message'</span>), $request-&gt;input(<span class="hljs-string">'signature'</span>), $request-&gt;input(<span class="hljs-string">'address'</span>));
        <span class="hljs-comment">// If $result is true, perform additional logic like logging the user in, or by creating an account if one doesn't exist based on the Ethereum address</span>
        <span class="hljs-keyword">return</span> ($result ? <span class="hljs-string">'OK'</span> : <span class="hljs-string">'ERROR'</span>);
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySignature</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $message, <span class="hljs-keyword">string</span> $signature, <span class="hljs-keyword">string</span> $address</span>): <span class="hljs-title">bool</span>
    </span>{
        $hash = Keccak::hash(sprintf(<span class="hljs-string">"\x19Ethereum Signed Message:\n%s%s"</span>, strlen($message), $message), <span class="hljs-number">256</span>);
        $sign = [
            <span class="hljs-string">'r'</span> =&gt; substr($signature, <span class="hljs-number">2</span>, <span class="hljs-number">64</span>),
            <span class="hljs-string">'s'</span> =&gt; substr($signature, <span class="hljs-number">66</span>, <span class="hljs-number">64</span>),
        ];
        $recid = ord(hex2bin(substr($signature, <span class="hljs-number">130</span>, <span class="hljs-number">2</span>))) - <span class="hljs-number">27</span>;

        <span class="hljs-keyword">if</span> ($recid != ($recid &amp; <span class="hljs-number">1</span>)) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        $pubkey = (<span class="hljs-keyword">new</span> EC(<span class="hljs-string">'secp256k1'</span>))-&gt;recoverPubKey($hash, $sign, $recid);
        $derived_address = <span class="hljs-string">'0x'</span> . substr(Keccak::hash(substr(hex2bin($pubkey-&gt;encode(<span class="hljs-string">'hex'</span>)), <span class="hljs-number">1</span>), <span class="hljs-number">256</span>), <span class="hljs-number">24</span>);

        <span class="hljs-keyword">return</span> (Str::lower($address) === $derived_address);
    }
}
</code></pre>
<p>There’s a lot going on in there, so let’s break it down:</p>
<h3 id="heading-create-the-message">Create the message</h3>
<p>The <code>message()</code> method creates the message we’ll supply to the front end. It also includes a random token to ensure the message to sign will be different each time.</p>
<p>This token is usually referred to as a nonce, or number used once. In this case, however, it’s a simple random string. </p>
<p>The purpose of this is to prevent <a target="_blank" href="https://en.wikipedia.org/wiki/Replay_attack">replay attacks</a> where, if a malicious user obtained your signature, they could use that to authenticate as you on the website.</p>
<p>The message is then saved to the session and returned to the front end.</p>
<h3 id="heading-verify-the-message">Verify the message</h3>
<p>Once you have signed the message with your private key via MetaMask, your Ethereum address as well as the signature is sent to the back end for verification.</p>
<p>If it passes the verification, we determine the Ethereum address that signed the message and ensure it matches the Ethereum address sent from the front end during the signing process.</p>
<p>If that passes, we send an <strong>OK</strong> or <strong>ERROR</strong> back to the front end.</p>
<p>It’s also at this point where you can add additional logic like logging the member in or creating a new member record if one doesn’t exist for the Ethereum address in question.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/Laravel-Metamask.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-finalise-the-front-end">Finalise the Front End</h2>
<p>Now that the backend is ready, we can complete the rest of the front end. This will involve launching MetaMask, asking the user to sign the message, and then verifying the signature by using our back-end route.</p>
<p>Below is the full <code>web3Login()</code> function:</p>
<pre><code class="lang-js">&lt;script&gt;
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">web3Login</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">window</span>.ethereum) {
            alert(<span class="hljs-string">'MetaMask not detected. Please install MetaMask first.'</span>);
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> ethers.providers.Web3Provider(<span class="hljs-built_in">window</span>.ethereum);

        <span class="hljs-keyword">let</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/web3-login-message'</span>);
        <span class="hljs-keyword">const</span> message = <span class="hljs-keyword">await</span> response.text();

        <span class="hljs-keyword">await</span> provider.send(<span class="hljs-string">"eth_requestAccounts"</span>, []);
        <span class="hljs-keyword">const</span> address = <span class="hljs-keyword">await</span> provider.getSigner().getAddress();
        <span class="hljs-keyword">const</span> signature = <span class="hljs-keyword">await</span> provider.getSigner().signMessage(message);

        response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/web3-login-verify'</span>, {
            <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">headers</span>: {
                <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
            },
            <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
                <span class="hljs-string">'address'</span>: address,
                <span class="hljs-string">'signature'</span>: signature,
                <span class="hljs-string">'_token'</span>: <span class="hljs-string">'{{ csrf_token() }}'</span>
            })
        });
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.text();

        <span class="hljs-built_in">console</span>.log(data);
    }
&lt;/script&gt;
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li>We first set the provider to the MetaMask provided <code>window.ethereum</code>.</li>
<li>Next, we grab the message returned from our back end (refresh the page a few times to try this out, you will notice the random token changes every time).</li>
<li>Once we have the message, we obtain the user’s Ethereum address and ask them to sign the message.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/image-3-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>We then POST the address and signature to the back end (along with our Laravel CRSF token) for verification.</li>
<li>The result is either an <strong>OK</strong> or <strong>ERROR</strong> string which we output in the console.</li>
<li>At this point you may elect to show an error message (if applicable) or redirect the user if they were registered or logged in during the back-end verification.</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial we covered the basics of adding a MetaMask login to your website. I hope this has proven useful! <a target="_blank" href="https://webdev.chowles.com/">Sign up to my newsletter</a> or <a target="_blank" href="https://www.chowles.com/">visit my blog</a> where I’ll share insightful web development articles to supercharge your skills.</p>
<p>Here are some ideas to take your integration one step further:</p>
<ul>
<li>Integrate a library like <a target="_blank" href="https://github.com/web3modal/web3modal">Web3Modal</a> to provide users with various wallet options instead of only MetaMask.</li>
<li>With the user’s Ethereum address validated, provide them with functions like displaying their ETH balance.</li>
</ul>
<h3 id="heading-resources">Resources</h3>
<ul>
<li>Download <a target="_blank" href="https://metamask.io/">MetaMask</a>.</li>
<li>View the <a target="_blank" href="https://docs.ethers.io/">ethers.js documentation</a>.</li>
<li>Latest <a target="_blank" href="https://laravel.com/docs">Laravel documentation</a>.</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
