Authentication Methods
The SDK provides simplified authentication methods that handle user registration, connection, session management, and logout through the HumanWallet class.
Methods Overview
connect()- Connect with existing passkeyregister()- Create new passkey-based accountsreconnect()- Restore previous sessionsdisconnect()- End sessions and clear datahasAccount()- Check if user has existing account
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
- Passkey Authentication: Browser prompts user to authenticate with their passkey
- Account Recovery: The smart contract wallet is recovered using the passkey
- Session Setup: The client automatically configures the session for immediate use
- State Update:
client.addressis 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
- Passkey Creation: Browser prompts user to create a passkey using biometrics/PIN
- Account Generation: A smart contract wallet is created using the passkey
- Session Setup: The client automatically configures the session for immediate use
- State Update:
client.addressis 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 operationsWhat 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