Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ClearSky - CDP + Wagmi Authentication System

A secure, decentralized authentication system built with Coinbase Developer Platform (CDP) and Wagmi, featuring signature-based device registration and multi-device support.


🎯 Overview

This project implements a two-step authentication flow that maintains the security properties of traditional wallet-based authentication while leveraging CDP's email-based wallet creation:

  1. CDP Email Authentication β†’ User verifies email and CDP creates an embedded wallet
  2. Signature Verification β†’ User signs a challenge to prove wallet ownership

This approach ensures:

  • βœ… Cryptographic proof of ownership (prevents spoofing)
  • βœ… Replay attack protection (single-use nonces)
  • βœ… Multi-device support (register multiple devices per wallet)
  • βœ… No browser extensions required (embedded wallet via CDP)

πŸš€ Features

  • CDP Email Authentication: Passwordless authentication using email OTP
  • Embedded Wallets: Non-custodial wallets created and managed by CDP
  • Wagmi Integration: Full compatibility with wagmi hooks for transactions
  • Signature-Based Auth: Cryptographic proof of wallet ownership
  • Device Management: Register and manage multiple devices
  • JWT Tokens: Secure session management with access + refresh tokens
  • Multi-Chain Support: Base and Base Sepolia networks
  • Type-Safe: Full TypeScript implementation

πŸ“‹ Prerequisites


πŸ› οΈ Installation

1. Clone and Install Dependencies

# Clone the repository
git clone <your-repo-url>
cd Client

# Install dependencies
npm install --legacy-peer-deps

Note: --legacy-peer-deps is required due to React 19 compatibility with CDP packages.

2. Configure Environment Variables

Copy the example environment file and update with your credentials:

cp .env.example .env

Edit .env with your CDP credentials:

# Get these from https://portal.cdp.coinbase.com
VITE_CDP_PROJECT_ID=your-project-id-here
VITE_CDP_API_BASE_PATH=https://api.cdp.coinbase.com

# Your backend API URL
VITE_API_BASE_URL=http://localhost:3000

# Optional
VITE_APP_LOGO_URL=https://your-logo-url.com/logo.png

3. Get Your CDP Project ID

  1. Sign in to CDP Portal
  2. Create or select a project
  3. Go to project settings (gear icon)
  4. Copy your Project ID
  5. Configure your allowed domain: http://localhost:5173 (for development)

πŸƒ Running the Application

Development Mode

npm run dev

The app will be available at http://localhost:5173

Build for Production

npm run build

Preview Production Build

npm run preview

πŸ—οΈ Architecture

Folder Structure

src/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ providers/          # CDP + Wagmi provider configuration
β”‚   β”œβ”€β”€ router/             # React Router setup
β”‚   └── store/              # Zustand state management
β”‚
β”œβ”€β”€ pages/                  # Route components
β”‚   β”œβ”€β”€ Landing/            # Authentication entry point
β”‚   └── Dashboard/          # Main application interface
β”‚
β”œβ”€β”€ components/             # Reusable UI components
β”‚   β”œβ”€β”€ ui/
β”‚   β”œβ”€β”€ layout/
β”‚   └── shared/
β”‚
β”œβ”€β”€ services/               # API and SDK integrations
β”‚   └── api/
β”‚       └── auth.service.ts # Authentication API calls
β”‚
β”œβ”€β”€ hooks/                  # Custom React hooks
β”‚   └── useAuth.ts          # Main authentication hook
β”‚
β”œβ”€β”€ utils/                  # Utility functions
β”‚   └── device.ts           # Device ID and info generation
β”‚
β”œβ”€β”€ config/                 # Configuration files
β”‚   β”œβ”€β”€ env.ts              # Environment variables
β”‚   └── routes.ts           # Route constants
β”‚
└── types/                  # TypeScript type definitions
    └── auth.types.ts       # Authentication types

πŸ”„ Authentication Flow

Visual Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Landing Page                         β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚         "Get Started" Button Clicked               β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              CDP Email Authentication (OTP)                 β”‚
β”‚                                                             β”‚
β”‚  1. User enters email                                       β”‚
β”‚  2. CDP sends OTP                                           β”‚
β”‚  3. User enters OTP                                         β”‚
β”‚  4. CDP creates embedded wallet                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               Wallet Created & Available                    β”‚
β”‚                                                             β”‚
β”‚  - Wallet address available via useAccount()                β”‚
β”‚  - isConnected = true                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Request Challenge from Backend                   β”‚
β”‚                                                             β”‚
β”‚  GET /auth/challenge                                        β”‚
β”‚  Response: { challenge, nonce, expiresAt }                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Sign Challenge with Wallet                       β”‚
β”‚                                                             β”‚
β”‚  - Use wagmi's signMessage hook                             β”‚
β”‚  - User confirms signature in UI                            β”‚
β”‚  - Get signature: 0x1234...                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Send Device Registration to Backend                 β”‚
β”‚                                                             β”‚
β”‚  POST /auth/device-register                                 β”‚
β”‚  {                                                          β”‚
β”‚    walletAddress: "0x742d35...",                            β”‚
β”‚    signature: "0x1234...",                                  β”‚
β”‚    message: "Sign this message...",                         β”‚
β”‚    deviceInfo: { deviceId, deviceName, userAgent }          β”‚
β”‚  }                                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Backend Verifies Signature                         β”‚
β”‚                                                             β”‚
β”‚  1. Extract nonce from message                              β”‚
β”‚  2. Verify challenge not expired                            β”‚
β”‚  3. Recover signer address from signature                   β”‚
β”‚  4. Verify signer matches walletAddress                     β”‚
β”‚  5. Delete nonce (prevent replay)                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Register Device & Generate Tokens                    β”‚
β”‚                                                             β”‚
β”‚  - Check if user exists (by walletAddress)                  β”‚
β”‚  - Create user if new                                       β”‚
β”‚  - Register or update device                                β”‚
β”‚  - Generate JWT access + refresh tokens                     β”‚
β”‚  - Return tokens + device list                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Store Tokens & Navigate to Dashboard              β”‚
β”‚                                                             β”‚
β”‚  - Store tokens in localStorage                             β”‚
β”‚  - Update Zustand auth state                                β”‚
β”‚  - Redirect to /dashboard                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Dashboard                               β”‚
β”‚                                                             β”‚
β”‚  - Display wallet info                                      β”‚
β”‚  - Show registered devices                                  β”‚
β”‚  - Access to authenticated features                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”‘ Key Components

1. WagmiProvider

Configures CDP and Wagmi integration:

// src/app/providers/WagmiProvider.tsx
const cdpConfig: Config = {
  projectId: env.CDP_PROJECT_ID,
  ethereum: {
    createOnLogin: "eoa", // Create EVM account on login
  },
  // ...other config
};

const connector = createCDPEmbeddedWalletConnector({
  cdpConfig,
  providerConfig: {
    chains: [base, baseSepolia],
    transports: { /* ... */ },
  },
});

2. useAuth Hook

Main authentication logic:

// src/hooks/useAuth.ts
export function useAuth() {
  const { address, isConnected } = useAccount(); // Wagmi
  const { signMessageAsync } = useSignMessage(); // Wagmi

  const completeDeviceRegistration = async () => {
    // 1. Request challenge
    const challenge = await requestChallenge();

    // 2. Sign challenge
    const signature = await signMessageAsync({
      message: challenge.challenge,
    });

    // 3. Register device
    const response = await registerDevice({
      walletAddress: address,
      signature,
      message: challenge.challenge,
      deviceInfo: getDeviceInfo(),
    });

    // 4. Store tokens
    storeTokens(response.tokens);
    setAuth(response);
  };

  return { completeDeviceRegistration, /* ... */ };
}

3. Landing Page

Handles the authentication flow:

// src/pages/Landing/index.tsx
export default function Landing() {
  const { isConnected, completeDeviceRegistration } = useAuth();

  useEffect(() => {
    if (isConnected && !isAuthenticated) {
      // After CDP auth, trigger device registration
      completeDeviceRegistration();
    }
  }, [isConnected]);

  return (
    <div>
      <AuthButton /> {/* CDP component */}
      {/* Loading states, error handling */}
    </div>
  );
}

πŸ” Security Features

Signature Verification

The signature verification step ensures:

  1. Proof of Ownership: Only the wallet owner can produce valid signatures
  2. No Spoofing: Attackers cannot register devices for other wallets
  3. Cryptographic Security: Uses EIP-191 standard message signing

Replay Attack Prevention

  • Single-use nonces: Each challenge can only be used once
  • Time-based expiration: Challenges expire after 5 minutes
  • Server-side validation: Backend deletes nonce after use

Device Management

  • Unique device IDs: Each device has a persistent identifier
  • Device tracking: Track registration and last usage times
  • Multi-device support: Users can access from multiple devices

🌐 Backend Integration

Required Endpoints

Your backend must implement these endpoints:

1. GET /auth/challenge

Generates a challenge for the user to sign.

Response:

{
  "challenge": "Sign this message to authenticate: 1733356800-abc123",
  "nonce": "abc123",
  "expiresAt": "2025-12-04T12:00:00Z"
}

2. POST /auth/device-register

Registers a device after verifying signature.

Request Body:

{
  "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "signature": "0x1234...",
  "message": "Sign this message to authenticate: 1733356800-abc123",
  "deviceInfo": {
    "deviceId": "uuid-here",
    "deviceName": "Chrome on MacOS",
    "userAgent": "Mozilla/5.0..."
  }
}

Response:

{
  "devices": [...],
  "limited": false,
  "tokens": {
    "access_token": "eyJhbGc...",
    "refresh_token": "eyJhbGc..."
  }
}

See BACKEND_VALIDATION_GUIDE.md for complete implementation details.


πŸ“š API Documentation

Authentication Service

Located at src/services/api/auth.service.ts

requestChallenge()

async function requestChallenge(): Promise<AuthChallenge>

Requests a challenge from the backend for signing.

registerDevice()

async function registerDevice(
  request: DeviceRegistrationRequest
): Promise<DeviceRegistrationResponse>

Registers a device with signature verification.

storeTokens() / getStoredTokens() / clearTokens()

Token management utilities for localStorage.


🎨 Customization

Theme Customization

Edit src/app/providers/WagmiProvider.tsx:

const themeOverrides = {
  "colors-bg-default": "#ffffff",
  "colors-bg-primary": "#0052ff",
  "colors-fg-default": "#000000",
  // ... more theme variables
};

<CDPReactProvider config={cdpConfig} theme={themeOverrides}>

See CDP Theming Documentation for all available theme tokens.

Adding Custom Hooks

Create hooks in src/hooks/:

// src/hooks/useWalletBalance.ts
export function useWalletBalance() {
  const { address } = useAccount();
  // Your custom logic
}

πŸ§ͺ Testing

Manual Testing Flow

  1. Start the dev server: npm run dev
  2. Navigate to landing page: http://localhost:5173
  3. Click "Get Started": Triggers CDP authentication
  4. Enter email: Receive OTP from CDP
  5. Enter OTP: Wallet is created
  6. Sign challenge: Approve signature request
  7. View dashboard: See wallet info and devices

Testing Backend Integration

# 1. Request challenge
curl http://localhost:3000/auth/challenge

# 2. Register device (use actual signature from frontend)
curl -X POST http://localhost:3000/auth/device-register \
  -H "Content-Type: application/json" \
  -d '{...}'

πŸ› Troubleshooting

Common Issues

1. "Wallet not connected"

Cause: CDP authentication didn't complete.

Solution:

  • Check CDP Project ID in .env
  • Verify domain is configured in CDP Portal
  • Check browser console for CDP errors

2. "Challenge not found or expired"

Cause: Challenge expired or was already used.

Solution:

  • Ensure backend challenge TTL is reasonable (5 minutes)
  • Check that challenge is deleted after use

3. "Signature verification failed"

Cause: Message doesn't match or signature is invalid.

Solution:

  • Ensure message is exactly the same on frontend and backend
  • Check that wallet address is normalized to lowercase
  • Verify signature format (0x prefix)

4. Peer dependency warnings

Cause: React 19 compatibility with CDP packages.

Solution:

  • Use --legacy-peer-deps flag when installing
  • This is expected and doesn't affect functionality

πŸ“– Additional Resources


πŸ“ License

[Your License Here]


🀝 Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.


πŸ“§ Support

For issues and questions:


Built with ❀️ using CDP + Wagmi

Releases

Packages

Contributors

Languages