@z21/web-security
v1.0.6
Published
Web3 authentication package for open world applications
Downloads
25
Readme
@z21/web-security
A React package for Web3 authentication using Wagmi and message signing, with built-in state management and configurable options.
Features
- 🔐 Secure Web3 authentication using message signing
- 🎛️ Configurable authentication flow with custom messages
- 🔄 Built-in state management with Zustand
- 🛡️ Auth guard components for protecting routes
- 🐛 Debug panel for development
- 📱 TypeScript support
- 🔌 Easy API client integration
- 🚫 Race condition prevention
- 🧾 Optional JWT authentication flow
- 🤖 Optional Google reCAPTCHA v3 verification
Installation
npm install @z21/web-securityPeer Dependencies
Make sure you have these installed in your project:
npm install react wagmi zustand lucide-react
# Optional (only if you enable reCAPTCHA integration)
npm install react-google-recaptcha-v3Basic Usage
1. Setup Your App
import { WagmiProvider } from 'wagmi'
import { config } from './wagmi-config' // Your Wagmi config
import { useWeb3Auth, AuthGuard } from '@z21/web-security'
function App() {
return (
<WagmiProvider config={config}>
<AuthGuard>
<YourAppContent />
</AuthGuard>
</WagmiProvider>
)
}2. Use the Authentication Hook (Web3)
import { useWeb3Auth } from '@z21/web-security'
import apiClient from './api/client' // Your axios instance
function LoginButton() {
const { authenticate, logout, isAuthenticated, isAuthenticating, authType } = useWeb3Auth()
const handleLogin = async () => {
const success = await authenticate(apiClient)
if (success) {
console.log('Authentication successful!')
}
}
const handleLogout = () => {
logout(apiClient)
}
if (isAuthenticated) {
return (
<div>
<div>Authenticated via: {authType}</div>
<button onClick={handleLogout}>Logout</button>
</div>
)
}
return (
<button onClick={handleLogin} disabled={isAuthenticating}>
{isAuthenticating ? 'Signing...' : 'Login with Wallet'}
</button>
)
}Advanced Configuration
Custom Authentication Configuration
import { useWeb3Auth, Web3AuthConfig } from '@z21/web-security'
const authConfig: Web3AuthConfig = {
appName: 'My DApp',
// Web3 server-side authorization check
authEndpoint: '/api/auth/verify',
// Optional: fetch the message to sign from your server
// If omitted, a default message will be generated locally
messageEndpoint: '/api/auth/message',
// Customize the message locally (used if messageEndpoint is not provided or fails)
customMessage: (chainId) => `Welcome to My DApp!\n\nChain ID: ${chainId}\nTimestamp: ${Date.now()}`,
// Optional: JWT flow endpoints (for non-wallet auth flows)
jwtIssueEndpoint: '/api/access-token/issue',
jwtProfileEndpoint: '/user-profile',
// Callbacks
onAuthSuccess: (authType) => {
console.log(`User authenticated successfully via ${authType}!`)
},
onAuthError: (error, authType) => {
console.error(`Authentication failed (${authType}):`, error)
},
onWeb3AuthSuccess: (headers) => {
console.log('Web3 headers set:', headers)
},
onJWTAuthSuccess: (token) => {
console.log('JWT token received:', token)
},
// Debug
enableDebug: process.env.NODE_ENV === 'development',
// reCAPTCHA (optional)
enableRecaptcha: true,
// pass executeRecaptcha from react-google-recaptcha-v3
// executeRecaptcha: yourExecuteRecaptchaFunction,
}
function MyComponent() {
const auth = useWeb3Auth(authConfig)
// ... rest of your component
}Custom Auth Guard Components
import { AuthGuard } from '@z21/web-security'
function MyApp() {
return (
<AuthGuard
loadingComponent={<div>Please sign the message...</div>}
unauthenticatedComponent={<div>Please connect your wallet</div>}
fallback={<div>Authentication required</div>}
>
<ProtectedContent />
</AuthGuard>
)
}AuthGuard considers both Web3 and JWT authentication states.
Authentication Headers
The package automatically manages authentication headers for API requests:
X-WAL: Wallet addressX-SIG: Message signatureX-MES: Raw message (not base64)
API Client Integration
import axios from 'axios'
import { applyAuthHeaders, useAuthHeaders, createAuthInterceptor, createAuthErrorInterceptor } from '@z21/web-security'
// Method 1: Manual header application
const apiClient = axios.create({ baseURL: '/api' })
const { authenticate, logout } = useWeb3Auth()
const handleAuth = async () => {
await authenticate(apiClient) // Headers applied automatically
}
// Method 2: Using interceptors
const authHeaders = useAuthHeaders()
createAuthInterceptor(apiClient, () => authHeaders)
// Optional: clear headers automatically on 401/403
createAuthErrorInterceptor(apiClient, () => console.log('Auth error – headers cleared'))State Management
The package uses Zustand for state management. You can access the auth store directly:
import { useAuthStore, useAuthHeaders, useJWTData, useIsAuthenticating, useAuthType } from '@z21/web-security'
function MyComponent() {
// Use individual selectors (recommended)
const authHeaders = useAuthHeaders()
const jwtData = useJWTData()
const isAuthenticating = useIsAuthenticating()
const authType = useAuthType()
// Or use the full store
const { authHeaders: headers, isAuthenticating: authing, setAuthHeaders } = useAuthStore()
return (
<div>
<p>Authenticated: {headers || jwtData ? 'Yes' : 'No'}</p>
<p>Auth Type: {authType || 'n/a'}</p>
<p>Authenticating: {authing ? 'Yes' : 'No'}</p>
</div>
)
}Debug Panel
For development, you can use the debug panel to monitor authentication state:
import { DebugPanel } from '@z21/web-security'
function App() {
return (
<div>
<YourApp />
{process.env.NODE_ENV === 'development' && <DebugPanel />}
</div>
)
}Toggle the debug panel in the browser console:
toggleWeb3AuthDebug()Optional: JWT Authentication
You can also authenticate via a JWT flow (no wallet signature), if you provide jwtIssueEndpoint:
import { useWeb3Auth } from '@z21/web-security'
function JWTLoginButton() {
const { authenticateJWT, isAuthenticating, isAuthenticated, authType } = useWeb3Auth({
jwtIssueEndpoint: '/api/access-token/issue',
enableDebug: true,
})
return (
<button disabled={isAuthenticating} onClick={() => authenticateJWT()}>
{isAuthenticated ? `Authenticated (${authType})` : (isAuthenticating ? 'Authorizing…' : 'Login with JWT')}
</button>
)
}reCAPTCHA Verification (optional)
If you enable reCAPTCHA v3, provide enableRecaptcha: true and pass executeRecaptcha from a provider such as react-google-recaptcha-v3.
Expected server endpoint (GET) to verify the token and return JSON, e.g. at /api/recaptcha/verify:
{ "success": true, "score": 0.9 }Hook return includes:
verifying: booleanaccessDenied: booleanrecaptchaVerified: booleanhandleVerify(): trigger verification manuallyresetVerification(): reset verification state
Note: If the verify endpoint responds with non-JSON or 404, verification is treated as unavailable.
Error Handling
The package provides comprehensive error handling:
const authConfig: Web3AuthConfig = {
onAuthError: (error, authType) => {
if (error.code === 4001) {
console.log(`[${authType}] User rejected the signature request`)
} else if (error.response?.status === 401) {
console.log(`[${authType}] Not authorized`)
} else if (error.response?.status === 403) {
console.log(`[${authType}] Access forbidden`)
} else {
console.log(`[${authType}] Authentication failed:`, error.message)
}
}
}API Reference
useWeb3Auth(config?: Web3AuthConfig)
Main hook for authentication (Web3 and JWT).
Parameters:
config(optional): Configuration object
Returns:
address: Connected wallet address (Web3)isConnected: Whether wallet is connected (Web3)authenticate(apiClient?): Authenticate via Web3 (message signing)authenticateJWT(): Authenticate via JWT flowisAuthenticated: Whether user is authenticated (Web3 or JWT)isAuthenticating: Whether authentication is in progressauthType: 'web3' | 'jwt' | nulllogout(apiClient?): Logout and clear auth stateauthHeaders: Current Web3 authentication headers (or null)jwtData: Current JWT data{ token, type: 'jwt' }(or null)verifying,accessDenied,recaptchaVerified: reCAPTCHA statehandleVerify(),resetVerification(): reCAPTCHA controls
AuthGuard
Component to protect routes based on authentication status.
Props:
children: Content to render when authenticatedloadingComponent: Custom loading componentunauthenticatedComponent: Custom component for unauthenticated statefallback: Custom fallback component
Configuration Types
interface Web3AuthConfig {
appName?: string
authEndpoint?: string
customMessage?: (chainId: number) => string
jwtIssueEndpoint?: string
jwtProfileEndpoint?: string
messageEndpoint?: string
onAuthSuccess?: (authType: 'web3' | 'jwt') => void
onAuthError?: (error: any, authType: 'web3' | 'jwt') => void
onJWTAuthSuccess?: (token: string) => void
onWeb3AuthSuccess?: (headers: AuthHeaders) => void
enableDebug?: boolean
executeRecaptcha?: (action: string) => Promise<string>
enableRecaptcha?: boolean
}Development
To build the package:
npm run buildTo watch for changes during development:
npm run devLicense
MIT
