Overview

Numpay delegates identity to Web3Auth (MetaMask Embedded Wallets). A successful one-time-code login produces two things on the client:
  1. A secp256k1 private key (the user’s EOA), reconstructed from shares held by Web3Auth’s Sapphire network. It never leaves the device.
  2. An ID token: a JWT signed by Web3Auth with ES256, whose identity claim is the verified phone number or email.
The key controls the user’s smart account. The ID token authenticates the user to the Numpay API.

Login configuration

Login and registration sequence

Token verification

The backend verifies every protected request in authenticateUser:
  • JWKS: https://api-auth.web3auth.io/jwks, fetched and cached by jose.
  • Audience check: without it, a token Web3Auth minted for any other app would verify. With it, only tokens for Numpay’s client ID pass.
  • Identity claim: v10 tokens use userId; older ones use verifierId. Both are accepted. The display name claim is never used as identity.
  • Result: req.user = { userId, phoneNumber? , email? }. Emails are trimmed and lowercased.

Rules the backend enforces

1

Identity comes only from the token

/user/register overwrites any phoneNumber or email in the body with the token’s values.
2

One identity, one wallet

phoneNumber, email and walletAddress are unique columns. Registering a wallet already owned by another identity returns 409.
3

Users edit only themselves

PUT /user/update/:id compares the target row’s phone or email with the token’s and returns 403 on mismatch. Wallet, phone and email can’t be changed through it.

Client session handling

  • The user record is cached in localStorage.user for instant reloads; /user/register is called again on every session restore, so it doubles as “sync me”.
  • An axios interceptor adds Authorization: Bearer <idToken> to every API call.
  • A 401 clears the cached user and redirects to /login.
  • Visiting a payment link while logged out stores the URL in redirectAfterLogin and returns to it after login.