Skip to content

API Authorization

Overview

This endpoint enables Web3 users to authenticate by connecting their wallet and signing a message. Upon successful verification, a JWT token is issued for authorizing subsequent API requests.

Authentication Flow

1. Frontend generates message with wallet address, chain ID, timestamp, and nonce
2. User signs the message using their wallet (personal_sign)
3. Frontend sends signed message to this endpoint
4. Backend verifies signature and wallet ownership
5. Backend issues JWT token
6. Frontend includes JWT token in Authorization header for all subsequent API calls

Endpoint Information

Endpoint: POST /api/v1/makers/connect

Description: Authenticate Web3 wallet connection and obtain JWT token for API authorization

Authentication Required: No (this is the authentication endpoint)

Request Parameters

FieldTypeRequiredDescription
wallet_addressstringYesEthereum-compatible wallet address (checksummed or lowercase)
chain_idstringYesBlockchain network chain ID (e.g., "1" for Ethereum mainnet, "97" for BSC testnet)
messagestringYesPlain text message that was signed by the wallet
signaturestringYesHex-encoded signature generated by personal_sign method

Message Format Specification

Template

text
Wallet address: {wallet_address}
ChainId: {chain_id}
Timestamp: {utc_timestamp}
Nonce: {unique_nonce}

Required Fields

FieldFormatDescriptionExample
wallet_addressEthereum addressMust match the signing wallet address0x1234567890123456789012345678901234567890
chain_idNumeric stringMust match the current blockchain network97
utc_timestampISO 8601 UTCCurrent UTC time, prevents replay attacks2025-11-12T01:59:44Z
nonceUUID or random stringUnique identifier, prevents replay attacksce7e949b-2018-4cb7-bac0-246e1c146c60

Format Rules

  • Each field must be on a separate line
  • Field name and value separated by : (colon + space)
  • Field order can be customized
  • Additional custom fields are allowed but will be ignored
  • Timestamp should be within reasonable time window (e.g., ±5 minutes)
  • Nonce must be unique per request

Message Example

text
Wallet address: 0x1234567890123456789012345678901234567890
ChainId: 97
Timestamp: 2025-11-12T01:59:44Z
Nonce: ce7e949b-2018-4cb7-bac0-246e1c146c60

Signature Generation

javascript
import { ethers } from 'ethers';

async function signMessage(walletAddress, chainId) {
	const provider = new ethers.BrowserProvider(window.ethereum);
	const signer = await provider.getSigner();

	const timestamp = new Date().toISOString().replace(/\.\d{3}/, '');
	const nonce = crypto.randomUUID();

	const message = `Wallet address: ${walletAddress}
ChainId: ${chainId}
Timestamp: ${timestamp}
Nonce: ${nonce}`;

	const signature = await signer.signMessage(message);

	return { message, signature };
}

Response Parameters

Success Response

FieldTypeDescription
codeintegerStatus code (0 = success)
dataobjectResponse data object
data.jwtstringJWT token for API authorization

JWT Token Structure

The JWT token contains:

  • Header: Algorithm and token type
  • Payload: Wallet address, chain ID, expiration time
  • Signature: Server-side signature for verification

Token expiration: Typically 24 hours (configurable by server)

Request Example

cURL

bash
curl -X POST "{API_BASE_URL}/api/v1/makers/connect" \
     -H "Content-Type: application/json" \
     -d '{
       "wallet_address": "0x1234567890123456789012345678901234567890",
       "chain_id": "97",
       "message": "Wallet address: 0x1234567890123456789012345678901234567890\nChainId: 97\nTimestamp: 2025-11-12T01:59:44Z\nNonce: ce7e949b-2018-4cb7-bac0-246e1c146c60",
       "signature": "0x8f3d2e1c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e01"
     }'

JavaScript (Fetch API)

javascript
const API_BASE_URL = process.env.API_BASE_URL; // Set based on environment

async function authenticate(walletAddress, chainId, message, signature) {
	const response = await fetch(`${API_BASE_URL}/api/v1/makers/connect`, {
		method: 'POST',
		headers: {
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({
			wallet_address: walletAddress,
			chain_id: chainId,
			message: message,
			signature: signature,
		}),
	});

	const data = await response.json();

	if (data.code === 0) {
		// Store JWT token
		localStorage.setItem('jwt_token', data.data.jwt);
		return data.data.jwt;
	} else {
		throw new Error(data.message || 'Authentication failed');
	}
}

Response Example

Success Response

json
{
	"code": 0,
	"data": {
		"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRfYWRkcmVzcyI6IjB4MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MCIsImNoYWluX2lkIjoiOTciLCJleHAiOjE3MzE0NTU5ODR9.K7Xz9Ym3Qw5Rt8Pn2Lv6Jh4Fg1Ds0Cx9Bw7Au5Nt3Km"
	}
}

Error Response

json
{
	"code": 400,
	"message": "Invalid signature"
}

Using JWT Token for API Authorization

After obtaining the JWT token, include it in the Authorization header for all subsequent API requests:

Header Format

Authorization: Bearer <jwt_token>

Example Request

bash
curl -X GET "{API_BASE_URL}/api/v1/orders" \
     -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

JavaScript Example

javascript
async function fetchOrders(jwtToken) {
	const response = await fetch(`${API_BASE_URL}/api/v1/orders`, {
		method: 'GET',
		headers: {
			Authorization: `Bearer ${jwtToken}`,
			'Content-Type': 'application/json',
		},
	});

	return await response.json();
}

Error Codes

CodeMessageDescriptionSolution
400Invalid signatureSignature verification failedEnsure message and signature match, check wallet connection
400Invalid message formatMessage doesn't contain required fieldsFollow message format specification
400Timestamp expiredTimestamp is too old or too far in futureGenerate new message with current timestamp
400Nonce already usedNonce has been used beforeGenerate new unique nonce
400Invalid wallet addressWallet address format is invalidProvide valid Ethereum address
400Chain ID mismatchChain ID doesn't match expected networkVerify correct chain ID for target network
401Token expiredJWT token has expiredRe-authenticate to obtain new token
401Invalid tokenJWT token is invalid or malformedRe-authenticate to obtain new token
429Too many requestsRate limit exceededWait before retrying
500Internal server errorServer-side error occurredContact support if persists

Security Best Practices

For Frontend Developers

  1. Secure Token Storage

    • Store JWT token in memory or httpOnly cookies
    • Avoid localStorage for sensitive applications
    • Clear token on logout
  2. Nonce Generation

    • Use cryptographically secure random values
    • Never reuse nonces
    • Use UUID v4 or similar
  3. Timestamp Validation

    • Use server time if available
    • Account for clock skew
    • Implement reasonable time windows
  4. Signature Verification

    • Always use personal_sign (not eth_sign)
    • Verify message content before signing
    • Display message to user clearly

For Backend Developers

  1. Signature Verification

    • Verify signature matches wallet address
    • Validate all message fields
    • Check timestamp within acceptable window
  2. Replay Attack Prevention

    • Store used nonces (with expiration)
    • Validate timestamp freshness
    • Implement rate limiting
  3. Token Management

    • Set appropriate expiration times
    • Use secure signing algorithms (HS256 or RS256)
    • Implement token refresh mechanism
  4. Rate Limiting

    • Limit authentication attempts per IP
    • Limit attempts per wallet address
    • Implement exponential backoff

Token Refresh

When JWT token expires, re-authenticate using this endpoint to obtain a new token. Consider implementing automatic token refresh before expiration:

javascript
async function refreshTokenIfNeeded(jwtToken) {
	// Decode JWT to check expiration
	const payload = JSON.parse(atob(jwtToken.split('.')[1]));
	const expiresAt = payload.exp * 1000; // Convert to milliseconds
	const now = Date.now();

	// Refresh if token expires in less than 1 hour
	if (expiresAt - now < 3600000) {
		// Re-authenticate to get new token
		const { message, signature } = await signMessage(walletAddress, chainId);
		return await authenticate(walletAddress, chainId, message, signature);
	}

	return jwtToken;
}

Complete Integration Example

javascript
import { ethers } from 'ethers';

class StockProtocolAuth {
	constructor(apiBaseUrl) {
		this.apiBaseUrl = apiBaseUrl;
		this.jwtToken = null;
	}

	async connect() {
		// Request wallet connection
		await window.ethereum.request({ method: 'eth_requestAccounts' });

		const provider = new ethers.BrowserProvider(window.ethereum);
		const signer = await provider.getSigner();
		const walletAddress = await signer.getAddress();
		const network = await provider.getNetwork();
		const chainId = network.chainId.toString();

		// Generate message
		const timestamp = new Date().toISOString().replace(/\.\d{3}/, '');
		const nonce = crypto.randomUUID();
		const message = `Wallet address: ${walletAddress}
ChainId: ${chainId}
Timestamp: ${timestamp}
Nonce: ${nonce}`;

		// Sign message
		const signature = await signer.signMessage(message);

		// Authenticate
		const response = await fetch(`${this.apiBaseUrl}/api/v1/makers/connect`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({
				wallet_address: walletAddress,
				chain_id: chainId,
				message: message,
				signature: signature,
			}),
		});

		const data = await response.json();

		if (data.code === 0) {
			this.jwtToken = data.data.jwt;
			return this.jwtToken;
		} else {
			throw new Error(data.message || 'Authentication failed');
		}
	}

	async apiCall(endpoint, options = {}) {
		if (!this.jwtToken) {
			throw new Error('Not authenticated. Call connect() first.');
		}

		const response = await fetch(`${this.apiBaseUrl}${endpoint}`, {
			...options,
			headers: {
				...options.headers,
				Authorization: `Bearer ${this.jwtToken}`,
				'Content-Type': 'application/json',
			},
		});

		return await response.json();
	}
}

// Usage
const API_BASE_URL = process.env.API_BASE_URL; // e.g., 'https://uat-api.example.com' or 'https://api.example.com'
const auth = new StockProtocolAuth(API_BASE_URL);
await auth.connect();
const orders = await auth.apiCall('/api/v1/orders');