Overview
Proteus uses wallet-based JWT authentication with two supported methods:
- MetaMask/External Wallet - Sign a message with your wallet
- Coinbase Embedded Wallet - Email OTP authentication
All authenticated requests include:
Authorization: Bearer <jwt_token>
Step 1: Get Nonce
Request a unique nonce for your wallet address.
curl https://proteus-production-6213.up.railway.app/auth/nonce/0x1234567890abcdef1234567890abcdef12345678
Your Ethereum wallet address (checksummed format recommended)
Unique 32-character hex string for this authentication attempt
Complete message to sign with your wallet
Response
{
"nonce": "abc123def456...",
"message": "Sign this message to authenticate with Proteus: abc123def456..."
}
Step 2: Sign Message
Sign the message with your wallet (client-side):
const signature = await ethereum.request({
method: 'personal_sign',
params: [message, address]
});
Step 3: Verify Signature
Submit the signature to receive a JWT token.
curl -X POST https://proteus-production-6213.up.railway.app/auth/verify \
-H "Content-Type: application/json" \
-d '{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"signature": "0xabcdef...",
"message": "Sign this message to authenticate with Proteus: abc123def456..."
}'
Your Ethereum wallet address
Hex-encoded signature from personal_sign
The exact message that was signed
JWT token for authenticated requests (expires in 24 hours)
Token expiry time in seconds (86400 for 24 hours)
Response
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"address": "0x1234567890abcdef1234567890abcdef12345678",
"expires_in": 86400
}
}
Coinbase Embedded Wallet Flow
Step 1: Request OTP
Send an OTP code to the user’s email.
curl -X POST https://proteus-production-6213.up.railway.app/api/embedded/auth/send-otp \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]"
}'
Response
{
"success": true,
"message": "OTP sent successfully"
}
Step 2: Verify OTP
Submit the 6-digit OTP code to authenticate.
curl -X POST https://proteus-production-6213.up.railway.app/api/embedded/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"otp": "123456"
}'
6-digit OTP code from email
Generated deterministic wallet address for this email
Response
{
"success": true,
"data": {
"email": "[email protected]",
"wallet_address": "0x9876543210fedcba9876543210fedcba98765432"
},
"message": "Successfully authenticated"
}
Token Management
Refresh Token
Refresh your JWT token before it expires.
curl -X POST https://proteus-production-6213.up.railway.app/auth/refresh \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.new_token...",
"address": "0x1234567890abcdef1234567890abcdef12345678",
"expires_in": 86400
}
}
Check Auth Status (JWT)
curl https://proteus-production-6213.up.railway.app/auth/jwt-status \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response
{
"authenticated": true,
"address": "0x1234567890abcdef1234567890abcdef12345678"
}
Check Auth Status (Session)
For Coinbase Embedded Wallet users with session cookies:
curl https://proteus-production-6213.up.railway.app/api/embedded/auth/status \
--cookie "session=..."
Response
{
"authenticated": true,
"email": "[email protected]",
"wallet_address": "0x9876543210fedcba9876543210fedcba98765432",
"wallet_type": "coinbase"
}
Logout
curl -X POST https://proteus-production-6213.up.railway.app/auth/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
For JWT-based auth, the client should discard the token on logout. The server logs the event but does not invalidate the token (stateless design).
Rate Limiting
OTP Request Limits
- 5 OTP requests per email per hour
- Returns
429 Too Many Requests if exceeded
OTP Verification Limits
- 5 failed attempts per email per hour
- Account locked for 1 hour after 5 failures
Error Response
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many OTP requests. Please try again later."
}
}
Security Notes
- Never share your JWT token or signature
- Tokens expire after 24 hours for security
- Nonces are single-use only (5 minute TTL)
- OTP codes expire after 10 minutes
- Always use HTTPS in production
Implementation Example
// MetaMask authentication
async function authenticateWithMetaMask() {
// 1. Get wallet address
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
// 2. Request nonce
const nonceRes = await fetch(`/auth/nonce/${address}`);
const { nonce, message } = await nonceRes.json();
// 3. Sign message
const signature = await ethereum.request({
method: 'personal_sign',
params: [message, address]
});
// 4. Verify signature
const verifyRes = await fetch('/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address, signature, message })
});
const { data } = await verifyRes.json();
return data.token; // Store this token for API requests
}
// Use the token
async function fetchMarketsAuthenticated(token) {
const res = await fetch('/api/chain/markets', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return res.json();
}