Skip to content

Authentication Methods

The SDK provides simplified authentication methods that handle user registration, connection, session management, and logout through the HumanWallet class.

Methods Overview

connect()

Connects with an existing passkey and automatically sets up the session.

Method Signature

async connect(): Promise<Address>

Return Value

Returns the authenticated wallet address as a promise.

Usage Example

import { HumanWallet } from "@humanwallet/sdk"
import { sepolia } from "viem/chains"
 
const client = new HumanWallet(
  "your-project-id",
  sepolia,
)
 
try {
  const address = await client.connect()
  console.log("Connected:", address)
  console.log("User is now authenticated:", !!client.address) // true
  console.log("Current address:", client.address) // same as returned address
} catch (error) {
  if (error.message.includes("User rejected")) {
    console.log("User cancelled passkey authentication")
  } else if (error.message.includes("not found")) {
    console.log("No passkey found - user needs to register first")
  } else {
    console.error("Connection failed:", error)
  }
}

What Happens During Connection

  1. Passkey Authentication: Browser prompts user to authenticate with their passkey
  2. Account Recovery: The smart contract wallet is recovered using the passkey
  3. Session Setup: The client automatically configures the session for immediate use
  4. State Update: client.address is set and user is ready to interact

register()

Creates a new passkey-based account for a user and automatically sets up the session.

Method Signature

async register(username: string): Promise<Address>

Parameters

  • username: Unique identifier for the user (used for passkey creation)

Return Value

Returns the newly created wallet address as a promise.

Usage Example

try {
  const address = await client.register("alice")
  console.log("Account created:", address)
  console.log("User is now authenticated:", !!client.address) // true
  console.log("Current address:", client.address) // same as returned address
} catch (error) {
  if (error.message.includes("User rejected")) {
    console.log("User cancelled passkey creation")
  } else if (error.message.includes("already exists")) {
    console.log("Username already taken")
  } else {
    console.error("Registration failed:", error)
  }
}

What Happens During Registration

  1. Passkey Creation: Browser prompts user to create a passkey using biometrics/PIN
  2. Account Generation: A smart contract wallet is created using the passkey
  3. Session Setup: The client automatically configures the session for immediate use
  4. State Update: client.address is set and user is ready to interact

reconnect()

Restores a previous session using stored authentication data, avoiding the need for passkey authentication.

Method Signature

async reconnect(): Promise<Address | null>

Return Value

Returns the restored wallet address if successful, or null if no session could be restored.

Usage Example

// Try to restore previous session on app startup
const restoredAddress = await client.reconnect()
 
if (restoredAddress) {
  console.log("Session restored for:", restoredAddress)
  console.log("User is authenticated:", client.isConnected) // true
 
  // User can immediately perform operations
  await client.writeContract(contractParams)
} else {
  console.log("No previous session found")
  console.log("User needs to login or register")
 
  // Show authentication UI
  await showAuthenticationDialog()
}

When to Use Reconnect

  • App Startup: Check if user has an active session
  • Page Refresh: Restore authentication state
  • Tab Switching: Maintain authentication across browser tabs
  • Background Tasks: Keep authentication for scheduled operations

disconnect()

Clears stored authentication data and ends the current session.

Method Signature

async disconnect(): Promise<void>

Usage Example

await client.disconnect()
 
console.log("User logged out")
console.log("Is connected:", client.isConnected) // false
console.log("Address:", client.address) // undefined
 
// All stored authentication data is cleared
// User will need to login/register again to perform operations

What Gets Cleared

  • Session keys and authentication tokens
  • Locally stored account data
  • Client state (address, connection status)
  • Any cached user information

hasAccount()

Checks if the user has an existing account stored locally without affecting the client's connection state.

Method Signature

async hasAccount(): Promise<boolean>

Usage Example

const userHasAccount = await client.hasAccount()
 
if (userHasAccount) {
  // Try to reconnect or show login UI
  const address = await client.reconnect()
  if (!address) {
    // Session expired, show login form
    showLoginForm()
  }
} else {
  // New user, show registration form
  showRegistrationForm()
}

Complete Authentication Flows

Registration Flow

class AuthenticationService {
  constructor(private client: ClientRepository) {}
 
  async registerNewUser(username: string): Promise<{ success: boolean; address?: string; error?: string }> {
    try {
      // Check if user already has an account
      const hasExisting = await this.client.hasAccount()
      if (hasExisting) {
        return { success: false, error: "User already has an account" }
      }
 
      // Register new user
      const address = await this.client.register(username)
 
      return { success: true, address }
    } catch (error) {
      return {
        success: false,
        error: this.handleAuthError(error as Error),
      }
    }
  }
 
  private handleAuthError(error: Error): string {
    if (error.message.includes("User rejected")) {
      return "Authentication cancelled by user"
    } else if (error.message.includes("not supported")) {
      return "Passkeys not supported in this browser"
    } else if (error.message.includes("already exists")) {
      return "Username already taken"
    } else {
      return "Authentication failed"
    }
  }
}

Login Flow

class LoginService {
  constructor(private client: ClientRepository) {}
 
  async authenticateUser(username: string): Promise<{ success: boolean; address?: string; error?: string }> {
    try {
      // First check if user has an account
      const hasAccount = await this.client.hasAccount()
      if (!hasAccount) {
        return { success: false, error: "No account found. Please register first." }
      }
 
      // Try to reconnect first (faster than full login)
      let address = await this.client.reconnect()
 
      if (!address) {
        // Session expired, perform full login
        address = await this.client.login(username)
      }
 
      return { success: true, address }
    } catch (error) {
      return {
        success: false,
        error: this.handleLoginError(error as Error),
      }
    }
  }
 
  private handleLoginError(error: Error): string {
    if (error.message.includes("User rejected")) {
      return "Authentication cancelled"
    } else if (error.message.includes("not found")) {
      return "Account not found"
    } else {
      return "Login failed"
    }
  }
}

Session Management

class SessionManager {
  constructor(private client: ClientRepository) {}
 
  async initializeSession(): Promise<{ authenticated: boolean; address?: string }> {
    try {
      // Try to restore existing session
      const address = await this.client.reconnect()
 
      if (address) {
        return { authenticated: true, address }
      }
 
      return { authenticated: false }
    } catch (error) {
      console.error("Session restoration failed:", error)
      return { authenticated: false }
    }
  }
 
  async logout(): Promise<void> {
    try {
      await this.client.disconnect()
    } catch (error) {
      console.error("Logout failed:", error)
    }
  }
 
  get isAuthenticated(): boolean {
    return this.client.isConnected
  }
 
  get userAddress(): string | undefined {
    return this.client.address
  }
}

React Hook Examples

Basic Authentication Hook

import { useState, useEffect } from "react"
import { ClientRepository } from "@humanwallet/sdk"
 
export function useAuth(client: ClientRepository) {
  const [address, setAddress] = useState<string>()
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState<string>()
 
  // Initialize session on mount
  useEffect(() => {
    async function initSession() {
      try {
        const restoredAddress = await client.reconnect()
        if (restoredAddress) {
          setAddress(restoredAddress)
        }
      } catch (err) {
        console.error("Session restoration failed:", err)
      } finally {
        setIsLoading(false)
      }
    }
 
    initSession()
  }, [client])
 
  const register = async (username: string) => {
    setIsLoading(true)
    setError(undefined)
 
    try {
      const newAddress = await client.register(username)
      setAddress(newAddress)
      return newAddress
    } catch (err) {
      const errorMessage = (err as Error).message
      setError(errorMessage)
      throw err
    } finally {
      setIsLoading(false)
    }
  }
 
  const login = async (username: string) => {
    setIsLoading(true)
    setError(undefined)
 
    try {
      const userAddress = await client.login(username)
      setAddress(userAddress)
      return userAddress
    } catch (err) {
      const errorMessage = (err as Error).message
      setError(errorMessage)
      throw err
    } finally {
      setIsLoading(false)
    }
  }
 
  const logout = async () => {
    setIsLoading(true)
 
    try {
      await client.disconnect()
      setAddress(undefined)
    } catch (err) {
      console.error("Logout failed:", err)
    } finally {
      setIsLoading(false)
    }
  }
 
  return {
    address,
    isAuthenticated: !!address,
    isLoading,
    error,
    register,
    login,
    logout,
  }
}

Complete Authentication Component

import React, { useState } from 'react'
import { useAuth } from './useAuth'
 
interface AuthComponentProps {
  client: ClientRepository
}
 
export function AuthComponent({ client }: AuthComponentProps) {
  const { address, isAuthenticated, isLoading, error, register, login, logout } = useAuth(client)
  const [username, setUsername] = useState('')
  const [mode, setMode] = useState<'login' | 'register'>('login')
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
 
    if (!username.trim()) {
      alert('Please enter a username')
      return
    }
 
    try {
      if (mode === 'register') {
        await register(username)
      } else {
        await login(username)
      }
    } catch (err) {
      // Error is handled by the hook
      console.error('Authentication failed:', err)
    }
  }
 
  if (isLoading) {
    return <div>Loading...</div>
  }
 
  if (isAuthenticated) {
    return (
      <div>
        <h2>Welcome!</h2>
        <p>Address: {address}</p>
        <button onClick={logout}>Logout</button>
      </div>
    )
  }
 
  return (
    <div>
      <h2>{mode === 'login' ? 'Login' : 'Register'}</h2>
 
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <button type="submit">
          {mode === 'login' ? 'Login' : 'Register'}
        </button>
      </form>
 
      {error && <p style={{ color: 'red' }}>{error}</p>}
 
      <button onClick={() => setMode(mode === 'login' ? 'register' : 'login')}>
        {mode === 'login' ? 'Need to register?' : 'Already have an account?'}
      </button>
    </div>
  )
}

Error Handling Best Practices

Comprehensive Error Handler

export class AuthErrorHandler {
  static getErrorMessage(error: Error): string {
    const message = error.message.toLowerCase()
 
    if (message.includes("user rejected")) {
      return "Authentication was cancelled. Please try again."
    }
 
    if (message.includes("not supported")) {
      return "Passkeys are not supported in this browser. Please use a modern browser."
    }
 
    if (message.includes("already exists")) {
      return "This username is already taken. Please choose a different one."
    }
 
    if (message.includes("not found")) {
      return "Account not found. Please check your username or register a new account."
    }
 
    if (message.includes("account not created")) {
      return "Failed to create account. Please try again."
    }
 
    if (message.includes("network")) {
      return "Network error. Please check your connection and try again."
    }
 
    return "Authentication failed. Please try again."
  }
 
  static isRetryableError(error: Error): boolean {
    const message = error.message.toLowerCase()
    return message.includes("network") || message.includes("timeout")
  }
 
  static isCancelledError(error: Error): boolean {
    return error.message.toLowerCase().includes("user rejected")
  }
}

Security Considerations

  • Username Privacy: Usernames are used for passkey creation but aren't stored on-chain
  • Session Security: Session keys are temporary and automatically expire
  • Local Storage: Authentication data is stored securely in browser storage
  • Network Security: All communications use HTTPS and secure protocols
  • Passkey Security: Private keys are stored in secure device enclaves and never exposed