One-click WordPress Login With MetaMask
In this tutorial I will show you how you can add “Login with MetaMask” button to your WordPress login page. Once you click on it, you will signed in to WordPress dashboard. Here is how it is going to work:

1. Create a WordPress Plugin
First things first, this whole web3 login thing will be done as a WordPress plugin. So, let’s begin with creating a plugin file structure:

Then let’s add something into rudr-simple-metamask-login.php
file:
<?php
/*
* Plugin name: MetaMask login by Misha
* Version: 1.0
* Author: Misha Rudrastyh
* Author URI: https://rudrastyh.com
*/
// the button itself
add_action( 'login_form', function() {
?>
<div id="mm-login">
<a href="" class="button">Login with MetaMask</a>
</div>
<?php
} );
// scripts and styles
add_action( 'login_enqueue_scripts', function() {
wp_enqueue_style( 'mm-login', plugin_dir_url( __FILE__ ) . 'assets/style.css' );
wp_enqueue_script( 'mm-login', plugin_dir_url( __FILE__ ) . 'assets/script.js', array(), time(), true );
} );
// AJAX request, we will need it later
add_action( 'wp_ajax_metamasklogin', 'rudr_ajax_login' );
add_action( 'wp_ajax_nopriv_metamasklogin', 'rudr_ajax_login' );
function rudr_ajax_login() {
echo 'ok';
die;
}
I tried to make it as simple as possible:
- We use
login_form
in order to add anything inside the WordPress login form (the hook fires just after the password field). - Hook
login_enqueue_scripts
allows to include CSS and JavaScript files on thewp-login.php
specifically. And here we will need a little CSS for the button and the JavaScript file that is going to work withetherium
library. - AJAX request is needed for verifying signature and signing the users in.
- One more thing – please take a look at
wp_enqueue_script()
function, it has the last parameter set totrue
, which means that it will be included right before the</body>
tag. Also I addedtime()
function to prevent it caching in browser.
So for now we have this:

But not without the CSS which goes to style.css
:
#mm-login{
text-align: center;
clear: both;
margin: 5px 0 15px;
}
#mm-login a.button{
background-image: url( 'metamask.svg' );
background-repeat: no-repeat;
background-size: 20px;
background-position: 10px 7px;
padding: 2px 10px 2px 38px;
}
2. Connect Wallet and Sign Message with etherium.request
And this step is much more interesting!
Create a message to sign
Let’s remember now how Nonces in WordPress work. Etherium signatures are pretty much the same! So we are going to create a nonce (signature) on front end (script.js
) and then before logging a user in we have to verify the signature on back end (when processing AJAX request).
In order to create a signature in Etherium we would need two things: a message to sign and an etherium wallet address.
If you check some NFT marketplaces for example, you will see that as a message to sign they are using just a text string with the date and time in it. Let’s do the same!
wp_register_script( 'mm-login', plugin_dir_url( __FILE__ ) . 'assets/script.js', array(), time(), true );
wp_localize_script(
'mm-login',
'metamask_login',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'message' => 'I want to login on rudrastyh.com at ' . date( 'Y-m-d H:i:s' ) . '. I accept the Terms of Service https://rudrastyh.com/privacy-policy.',
)
);
wp_enqueue_script( 'mm-login' );
For the simplicity I decided to add this message in PHP. Anyway we will need ajaxurl
, so wp_localize_script()
function perfectly does these two things at once.
Check if MetaMask extension is installed
Before doing anything else, please make sure that you have installed and activated MetaMask extension for your browser. For example I am using Chrome and here it is:

The browser extension itself allows us to use window.ethereum
API.
const loginButton = document.querySelector( '#mm-login a' )
if( 'undefined' !== typeof window.ethereum ) {
// click on button
loginButton.addEventListener( 'click', async function ( event ) {
event.preventDefault()
web3SignIn() // we are going to talk about it in a while
} )
} else {
// MetaMask is not installed
loginButton.remove() // remove "Login with MetaMask" button
}
Sign the message and send the signature into the AJAX request
The function below is going to perform 3 things:
- Connects to a wallet, if it is not connected and requests etherium address.
- Signs the message.
- Sends an AJAX request and depending on the result it either redirects users to /wp-admin or displays an error.
async function web3SignIn() {
// let's remove login errors if any
const loginError = document.getElementById( 'login_error' )
if( loginError ) {
loginError.remove()
document.loginform.classList.remove( 'shake' )
}
// connect to wallet and request etherium address
let address = ( await ethereum.request({ method: 'eth_requestAccounts' }) )[0];
// signing the signature
const sign = await ethereum.request({
method: 'personal_sign',
params: [
`0x${buffer.Buffer.from(metamask_login.message, 'utf8').toString('hex')}`,
address
],
});
// sending AJAX request to WordPress
await jQuery.ajax({
method: 'POST',
url: metamask_login.ajaxurl,
data: {
action: 'metamasklogin',
address: address,
message: metamask_login.message,
signature: sign
},
success: function( response ) {
// console.log( response );
if( true === response.success ) {
// show success message
document.loginform.insertAdjacentHTML( 'beforebegin', '<div class="success"><strong>Success:</strong> Redirecting...</div>' );
// redirect to admin
window.location.href = '/wp-admin';
} else {
// shake the form and show error message
document.loginform.classList.add( 'shake' );
document.loginform.insertAdjacentHTML( 'beforebegin', '<div id="login_error"><strong>Error:</strong> ' + response.data[0].message + '</div>' );
}
}
});
}
Things to keep in mind:
- If you just insert the code as is, you might probably get a console error “Uncaught ReferenceError: Buffer is not defined”. Here is two things you can do – either install it as NPM package into your project
npm i buffer
and thenrequire('buffer')
or just include it as a standalone script from herehttps://bundle.run/buffer@6.0.3
. - You can also see jQuery here. I know some of you don’t like it, I have nothing against jQuery, moreover it is already included on WordPress default login page, so we can use it.
Now we have this window appear when we click “Login with MetaMask” button:

3. Verifying Etherium Signature and Logging In
Here let’s assume that we don’t have to register users if they are not registered yet (for simplicity of this tutorial) and we already have the users in WordPress database, and their etherium wallet addresses are stored in wp_usermeta
.
For example we could even add a field to user profiles like this:

In case you decide to use my plugin here is how you can add this field.
add_filter( 'simple_register_user_settings', function( $settings ) {
$settings[] = array(
'id' => 'eth_settings',
'fields' => array(
array(
'id' => 'eth_address',
'label' => 'Etherium address',
),
)
);
return $settings;
} );
And our goal here, once we verify the etherium signature is to get a specific user by a ETH address and authorize then.
Elliptic Curves Functions
Let’s do some elliptic curves mathematics!
If you have no idea what is happening here you can just insert the code as is. The only thing to keep in mind is that you have to include additional libraries, here is the list of them you can find on GitHub: simplito/elliptic-php
, kornrunner/php-keccak
, simplito/bigint-wrapper-php
and simplito/bn-php
. You can use Composer to install the libraries.
use Elliptic\EC;
use kornrunner\Keccak;
function rudrPubKeyToAddress( $pubkey ) {
return "0x" . substr( Keccak::hash( substr( hex2bin( $pubkey->encode( "hex" ) ), 1 ), 256 ), 24 );
}
function rudrVerifySignature( $message, $signature, $address ) {
$len = strlen( $message );
// the message is prepended with \x19Ethereum Signed Message:\n<length of message>
$hash = Keccak::hash( "\x19Ethereum Signed Message:\n{$len}{$message}", 256 );
$sign = array(
"r" => substr( $signature, 2, 64 ),
"s" => substr( $signature, 66, 64 )
);
$recid = ord( hex2bin( substr( $signature, 130, 2 ) ) ) - 27;
if( $recid != ( $recid & 1 ) ) {
return false;
}
$ec = new EC( 'secp256k1' );
$pubkey = $ec->recoverPubKey( $hash, $sign, $recid );
return strtolower( $address ) == strtolower( rudrPubKeyToAddress( $pubkey ) );
}
Logging users in
I hope you’re doing just great with the code, because it is the last step.
add_action( 'wp_ajax_metamasklogin', 'rudr_ajax_login' );
add_action( 'wp_ajax_nopriv_metamasklogin', 'rudr_ajax_login' );
function rudr_ajax_login() {
$address = isset( $_POST[ 'address' ] ) ? $_POST[ 'address' ] : '';
$message = isset( $_POST[ 'message' ] ) ? $_POST[ 'message' ] : '';
$signature = isset( $_POST[ 'signature' ] ) ? $_POST[ 'signature' ] : '';
if( rudrVerifySignature( $message, $signature, $address ) ) {
$users = get_users(
array(
'meta_key' => 'eth_address',
'meta_value' => $address,
'number' => 1
)
);
if( ! is_wp_error( $users ) && ( $user = reset( $users ) ) ) {
// signing the user in
wp_clear_auth_cookie();
wp_set_current_user( $user->ID );
wp_set_auth_cookie( $user->ID );
wp_send_json_success( array( 'message' => 'Ok' ) );
} else {
wp_send_json_error( new WP_Error( 'login_err', 'No such user' ) );
}
} else {
wp_send_json_error( new WP_Error( 'login_err', 'Verification failed' ) );
}
}
This piece of code is so simple I don’t even know what to clarify here. And here we are:

That’s all guys! If you need some custom web3 development, you can contact me anytime.
Or maybe you would like a plugin for it? Just let me know in the comments below.

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
Weaw