API Authentication
Authentication Flow
TimeProof uses Sign-In with Ethereum (SIWE) — an open standard for wallet-based authentication. The entire flow happens in three steps, and the result is a session cookie that authenticates all subsequent requests.
Step 1: Request a Nonce
GET /api/auth/nonce
The server generates a random nonce (a one-time challenge string) and returns it. This nonce prevents replay attacks — each authentication attempt requires a fresh one.
{
"nonce": "a1b2c3d4e5f6..."
}
Step 2: Create and Sign a SIWE Message
Construct a SIWE message that includes the nonce, your wallet address, the domain, and a timestamp. Then sign it with your wallet’s private key.
The SIWE message format follows EIP-4361:
app.timeprooflabs.com wants you to sign in with your Ethereum account:
0xYourWalletAddress
Sign in to TimeProof
URI: https://app.timeprooflabs.com
Version: 1
Chain ID: 137
Nonce: a1b2c3d4e5f6...
Issued At: 2026-02-03T12:00:00Z
Sign this message using your wallet (MetaMask, WalletConnect, or programmatically with ethers.js).
Step 3: Submit the Signature
POST /api/auth/verify
Content-Type: application/json
{
"message": "app.timeprooflabs.com wants you to sign in...",
"signature": "0x..."
}
The server verifies the signature matches the wallet address in the message. On success, it sets an HTTP-only session cookie and returns:
{
"success": true,
"address": "0xYourWalletAddress",
"chainId": 80002
}
From this point, every request that includes the session cookie is authenticated as that wallet address.
Session Management
Checking Your Session
GET /api/auth/session
Returns the current session state. If the session is valid, you’ll see your wallet address and chain ID. If expired or not authenticated, you’ll get a response indicating no active session.
Signing Out
POST /api/auth/signout
Destroys the session and clears the cookie. The alias POST /api/auth/logout does the same thing.
Session Expiration
Sessions expire after a period of inactivity. When you receive a 401 Unauthorized response, re-authenticate by repeating the three-step flow. The session cookie is HTTP-only, meaning JavaScript cannot access it directly — this is a security feature that prevents XSS attacks from stealing sessions.
Code Examples
JavaScript (ethers.js v6)
import { BrowserProvider } from 'ethers';
// Step 1: Get nonce
const nonceRes = await fetch('/api/auth/nonce', {
credentials: 'include'
});
const { nonce } = await nonceRes.json();
// Step 2: Create and sign SIWE message
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const message = `app.timeprooflabs.com wants you to sign in with your Ethereum account:
${address}
Sign in to TimeProof
URI: ${window.location.origin}
Version: 1
Chain ID: 137
Nonce: ${nonce}
Issued At: ${new Date().toISOString()}`;
const signature = await signer.signMessage(message);
// Step 3: Verify
const verifyRes = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ message, signature })
});
const result = await verifyRes.json();
// { success: true, address: "0x...", chainId: 80002 }
Python (web3.py)
import requests
from web3 import Web3
from eth_account.messages import encode_defunct
from datetime import datetime
session = requests.Session()
base = "http://localhost:5050"
# Step 1: Get nonce
nonce = session.get(f"{base}/api/auth/nonce").json()["nonce"]
# Step 2: Sign message
w3 = Web3()
account = w3.eth.account.from_key("0xYOUR_PRIVATE_KEY")
message_text = f"""app.timeprooflabs.com wants you to sign in with your Ethereum account:
{account.address}
Sign in to TimeProof
URI: {base}
Version: 1
Chain ID: 137
Nonce: {nonce}
Issued At: {datetime.utcnow().isoformat()}Z"""
msg = encode_defunct(text=message_text)
signed = account.sign_message(msg)
# Step 3: Verify
result = session.post(f"{base}/api/auth/verify", json={
"message": message_text,
"signature": signed.signature.hex()
}).json()
# Now all requests via 'session' are authenticated
Security Considerations
Why SIWE Is Secure
Your private key never leaves your wallet. The server receives only a signature — a mathematical proof that you control the wallet. Even if the signature is intercepted, it cannot be reused because:
- The nonce is single-use
- The message includes a timestamp and expiration
- The signature is bound to a specific domain
Cookie Security
The session cookie is configured with:
| Attribute | Value | Purpose |
|---|---|---|
| HttpOnly | true | Prevents JavaScript access (XSS protection) |
| Secure | true (production) | Cookie sent only over HTTPS |
| SameSite | Strict | Prevents CSRF attacks |
| Path | /api | Cookie sent only to API routes |
Protecting Your Session
- Don’t share your session cookie with untrusted code
- Sign out when you’re done (especially on shared devices)
- If you suspect compromise, sign out immediately — this invalidates the session server-side
Troubleshooting Authentication
| Problem | Solution |
|---|---|
| 401 on every request | Session expired — re-authenticate |
| Signature verification fails | Ensure the message text matches exactly what was signed |
| Nonce expired | Request a fresh nonce — they’re single-use and time-limited |
| CORS errors | Ensure credentials: 'include' is set in fetch requests |
| Cookie not sent | Check that SameSite policy matches your domain setup |
Related Guides
- API Overview — introduction to the full API
- Wallet Setup Guide — setting up MetaMask or external wallet
- API Timestamp Endpoints — next step after authenticating
- Security Architecture — how TimeProof protects your data
Use the live product for timestamping and verification.
The company site owns the technical reference. The app handles runtime workflows.