MetaMask Integration for Website Authorization (Web3 Login)

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

MetaMask Integration for Website Authorization (Web3 Login)

Web3 Login via MetaMask allows users to authorize on a website by signing a message with their wallet's cryptographic key. No passwords — proof of address ownership through signature.

Authorization Mechanism

  1. Frontend requests nonce from server for wallet address
  2. MetaMask shows user a message to sign
  3. User signs — MetaMask returns signature
  4. Server verifies signature and issues JWT

Frontend: Connecting MetaMask

import { ethers } from 'ethers';

async function loginWithMetaMask(): Promise<void> {
  // 1. Check for MetaMask
  if (!window.ethereum) {
    throw new Error('MetaMask not installed');
  }

  // 2. Request account access
  const provider = new ethers.BrowserProvider(window.ethereum);
  await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();
  const address = await signer.getAddress();

  // 3. Get nonce from server
  const nonceResponse = await fetch(`/api/auth/nonce?address=${address}`);
  const { nonce } = await nonceResponse.json();

  // 4. Sign message
  const message = `Sign in to example.com\n\nNonce: ${nonce}\nTime: ${new Date().toISOString()}`;
  const signature = await signer.signMessage(message);

  // 5. Send signature to server
  const authResponse = await fetch('/api/auth/web3', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ address, signature, message })
  });

  const { token } = await authResponse.json();
  localStorage.setItem('auth_token', token);
}

Backend: Signature Verification

// Node.js + ethers.js
import { ethers } from 'ethers';
import { randomBytes } from 'crypto';

// Nonce storage (Redis with 5 min TTL)
async function getNonce(address: string): Promise<string> {
  const normalized = address.toLowerCase();
  const existing = await redis.get(`nonce:${normalized}`);
  if (existing) return existing;

  const nonce = randomBytes(16).toString('hex');
  await redis.setex(`nonce:${normalized}`, 300, nonce);
  return nonce;
}

// Verification
async function verifyWeb3Auth(req, res) {
  const { address, signature, message } = req.body;
  const normalized = address.toLowerCase();

  // Check nonce in message
  const storedNonce = await redis.get(`nonce:${normalized}`);
  if (!storedNonce || !message.includes(storedNonce)) {
    return res.status(401).json({ error: 'Invalid or expired nonce' });
  }

  // Recover address from signature
  const recoveredAddress = ethers.verifyMessage(message, signature).toLowerCase();

  if (recoveredAddress !== normalized) {
    return res.status(401).json({ error: 'Signature verification failed' });
  }

  // Delete used nonce
  await redis.del(`nonce:${normalized}`);

  // Find or create user
  let user = await userRepo.findByWalletAddress(normalized);
  if (!user) {
    user = await userRepo.create({ walletAddress: normalized });
  }

  const token = jwt.sign(
    { sub: user.id, walletAddress: normalized },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.json({ token, userId: user.id });
}

Supporting Multiple Wallets

// Link additional wallet to account
async function linkWallet(userId: string, address: string, signature: string) {
  const existing = await walletRepo.findByAddress(address.toLowerCase());
  if (existing) throw new Error('Wallet already linked to another account');

  await walletRepo.create({
    userId,
    address: address.toLowerCase(),
    linkedAt: new Date()
  });
}

Timeline

MetaMask Login with nonce verification and JWT — 2–3 days.