Skip to main content

Passwordless Authentication with MetaMask & Nillion

Introduction

This guide demonstrates how to build a passwordless authentication system using MetaMask for identity and Nillion's Network User Credentials (NUC) for secure, decentralized authentication. Instead of traditional passwords, users authenticate using their Ethereum wallet signatures.

MetaMask for Decentralized Identity (DID)

What is a DID?

A Decentralized Identifier (DID) is a globally unique identifier that doesn't require a centralized authority. In our implementation, MetaMask wallet signatures are used to generate a DID that uniquely identifies users across the Nillion network.

What are NUCs (Network User Credentials)?

NUCs are Nillion's cryptographic credentials that enable:

  • Passwordless authentication via wallet signatures
  • Secure access to Nillion's privacy-preserving infrastructure
  • Identity verification without storing passwords or sensitive data

The NUC SDK abstracts the complexity of cryptographic operations, providing a simple interface for Web3 authentication.


Full code on github Here

cd nillion-mm-demo
pnpm install
pnpm dev

Step By Step Implementation

Prerequisites

This guide assumes you have a nextjs app already set up. Otherwise install NextJS:

npx create-next-app@latest my-app --yes

Install the required dependencies:

npm install @nillion/nuc @nillion/secretvaults viem @tanstack/react-query

Active NilDB Subcription

Head over to subscription.nillion.com and subscribe to Nildb.


1. Configure Nillion Network

Create a configuration file for Nillion network endpoints:

// src/config.ts
export const NETWORK_CONFIG = {
chainId: "nillion-chain-testnet-1",
nilchain: "http://rpc.testnet.nilchain-rpc-proxy.nilogy.xyz",
nilauth: "https://nilauth.sandbox.app-cluster.sandbox.nilogy.xyz",
nildb: [
"https://nildb-stg-n1.nillion.network",
"https://nildb-stg-n2.nillion.network",
"https://nildb-stg-n3.nillion.network",
],
};

2. Connect MetaMask & Create NUC Signer

Set up the MetaMask connection and NUC signer:

// src/context/NillionContext.tsx
import { Signer } from "@nillion/nuc";
import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";

const connectMetaMask = async () => {
if (!window.ethereum) {
throw new Error("MetaMask not installed");
}

// Create wallet client with viem
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum),
});

const [account] = await walletClient.requestAddresses();

// Create Nillion NUC Signer from MetaMask
const nucSigner = Signer.fromWeb3({
getAddress: async () => account,
signTypedData: async (domain, types, message) => {
return walletClient.signTypedData({
account,
domain,
types,
primaryType: Object.keys(types).find(k => k !== "EIP712Domain"),
message,
});
},
});

// Generate DID from signer
const did = await nucSigner.getDid();
console.log("User DID:", did.didString);

return { signer: nucSigner, did: did.didString, address: account };
};

3. Initialize Nillion Session

Use the NUC signer to authenticate with Nillion:

// src/hooks/useNillionClient.ts
import { NillionClient } from "@nillion/secretvaults";
import { NETWORK_CONFIG } from "@/config";

async function initializeSession(signer: Signer) {
const client = new NillionClient({
signer,
nilchain: NETWORK_CONFIG.nilchain,
nilauth: NETWORK_CONFIG.nilauth,
nildb: NETWORK_CONFIG.nildb,
});

// Initialize session - this authenticates the user
await client.initialize();

return client;
}

4. Register & Subscribe User

Complete the authentication flow by registering the user:

// Register builder profile
async function registerBuilder(client: NillionClient) {
await client.registerBuilder({
name: "User",
description: "Authenticated via MetaMask",
});
}

// Subscribe to Nillion services
async function subscribe(client: NillionClient) {
await client.subscribe({
name: "notes_subscription",
subscriptionType: "free",
});
}

5. Login Flow (Returning Users)

For returning users with stored sessions:

// src/hooks/useLoginMutation.ts
async function login(signer: Signer, rootToken: string, nildbTokens: object) {
const client = new NillionClient({
signer,
nilchain: NETWORK_CONFIG.nilchain,
nilauth: NETWORK_CONFIG.nilauth,
nildb: NETWORK_CONFIG.nildb,
});

// Restore previous session
await client.login({
rootToken,
nildbTokens,
});

return client;
}

6. Complete Authentication Flow

Wire everything together:

// Simplified flow
async function authenticateUser() {
// 1. Connect MetaMask & get NUC signer
const { signer, did } = await connectMetaMask();

// 2. Check if user has existing session
const hasSession = checkLocalStorage();

if (hasSession) {
// Login with existing session
await login(signer, storedRootToken, storedNildbTokens);
} else {
// Initialize new session
const client = await initializeSession(signer);

// Register and subscribe
await registerBuilder(client);
await subscribe(client);

// Store session tokens
saveToLocalStorage(client.tokens);
}

// User is now authenticated!
return { did, authenticated: true };
}

Key Benefits

No Password Storage - Users authenticate with cryptographic signatures
Decentralized Identity - DIDs are portable across applications
Enhanced Security - Leverages battle-tested wallet security
Better UX - One-click authentication for Web3 users
Privacy-First - Built on Nillion's privacy-preserving infrastructure


Conclusion

This implementation demonstrates how MetaMask and Nillion's NUC SDK enable passwordless authentication in Web3 applications. By leveraging wallet signatures and decentralized identifiers, you can build secure, user-friendly authentication without the risks of traditional password systems.

The NUC abstraction handles all cryptographic complexity, allowing developers to focus on building great user experiences while maintaining the highest security standards.