Simple Login


This example provides a step-by-step guide to implementing a basic user login system using DID Connect. The goal is to authenticate a user with their DID Wallet and retrieve their basic profile information, such as their name and email address. This is one of the most common use cases for the library.

We will use WalletHandlers to integrate with an Express.js server and request a profile claim from the user.

How It Works#

The login process follows the standard DID Connect workflow:

  1. Initialization: The backend sets up a WalletAuthenticator with the application's identity and a WalletHandlers instance to manage sessions.
  2. Handler Attachment: A specific handler is attached to an Express route (e.g., /api/did/login). This handler defines the information to be requested (claims) and the logic to execute upon successful authentication (onAuth).
  3. User Interaction: The frontend displays a DID Connect button or QR code, initiating the session with the backend.
  4. Wallet Approval: The user scans the QR code with their DID Wallet, reviews the request (to share their profile), and approves it.
  5. Backend Callback: The onAuth callback on the server is triggered, receiving the user's DID (userDid) and their profile data.
  6. Session Creation: The application can now use this information to create a user session, issue a token, or save the user's profile to a database.

Implementation#

Here’s how to build a simple login endpoint.

Step 1: Initialize Authenticator and Handlers#

First, you need to configure the WalletAuthenticator with your application's details and the WalletHandlers to manage the session state.

Simple Login Setup

const express = require('express');
const { fromRandom } = require('@ocap/wallet');
const { WalletAuthenticator, WalletHandlers } = require('@arcblock/did-connect-js');
const SimpleStorage = require('@arcblock/did-connect-storage-nedb');

// 1. Create a wallet for the application
const wallet = fromRandom();

// 2. Configure the authenticator with app and chain info
const authenticator = new WalletAuthenticator({
  wallet,
  baseUrl: 'http://localhost:3000', // Your application's base URL
  appInfo: {
    name: 'Simple Login Example',
    description: 'A simple example to demonstrate DID Connect login',
    icon: '/images/logo.png', // A URL to your app's icon
  },
  chainInfo: {
    host: 'https://beta.abtnetwork.io/api',
    id: 'beta',
  },
});

// 3. Initialize handlers with the authenticator and a storage adapter
const handlers = new WalletHandlers({

See all 9 lines

  • WalletAuthenticator: This object holds your application's identity (wallet) and metadata (appInfo, chainInfo) that will be displayed to the user in their wallet.
  • WalletHandlers: This class orchestrates the DID Connect session. It requires an authenticator and a tokenStorage adapter to persist session state. did-connect-storage-nedb is a simple file-based storage suitable for examples.

Step 2: Attach the Login Handler#

Next, use the handlers.attach() method to create the API endpoints and define the logic for your login flow. This method automatically creates routes like /api/did/login/token and /api/did/login/status for you.

Attach Login Handler

handlers.attach(app, {
  // The base path for the DID Connect endpoints
  // e.g., /api/did/login, /api/did/profile
  action: 'login',

  // Define the claims to request from the user's wallet
  claims: {
    profile: () => ({
      fields: ['fullName', 'email'],
      description: 'Please provide your name and email to log in.',
    }),
  },

  // The callback function executed upon successful authentication
  onAuth: async ({ userDid, claims }) => {
    // userDid: The DID of the authenticated user.
    // claims: An array of claims submitted by the user.
    try {
      const profile = claims.find((x) => x.type === 'profile');
      console.log('Login successful!', { userDid, profile });

      // In a real application, you would typically:
      // 1. Find or create a user in your database with the userDid.
      // 2. Store the profile information.
      // 3. Create a session or issue a JWT for the user.

See all 12 lines

  • action: 'login': This sets the base path for the generated endpoints to /api/did/login.
  • claims.profile: This object specifies that we are requesting a profile claim. The fields array lists the specific pieces of information we need, and the description is shown to the user in their wallet to explain why the information is needed.
  • onAuth: This is the most critical part. This function runs only after the user has successfully authenticated and approved the request. It receives the userDid and the claims data. Here, we simply log the information, but in a real-world application, this is where you would handle user session management.

After running this server, you can integrate the frontend using the DID Connect UX library, pointing it to the /api/did/login endpoint to initiate the login flow.

Next Steps#

Now that you have a basic login system, you can explore more advanced features: