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:

Login flow with MetaMask for WordPress
It is pretty much how it is going to work. The only thing, once you’ve already connected MetaMask there are going to be less steps – just “Signature request” modal window, that’s all.

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:

web3 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:

So for now we have this:

MetaMask login button in WordPress

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:

MetaMask extension in Google Chrome

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:

  1. Connects to a wallet, if it is not connected and requests etherium address.
  2. Signs the message.
  3. 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:

Now we have this window appear when we click “Login with MetaMask” button:

MetaMask signature request

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:

etherium address field in WordPress user profiles
In order to add a field like this into user profiles I recommend to use my Simple Fields plugin.

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:

Login flow with MetaMask for WordPress

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

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

Follow me on Twitter