Authentication
Developer documentation: where to get current user data and how the server session differs from the wallet and Nado.
What counts as an authenticated user
After Sign-In with Ethereum (SIWE), the gateway sets an HTTP-only cookie named thornado_auth containing a JWT. The token stores only one identifier: the wallet's Ethereum address in lowercase, 0x + 40 hex characters.
There are no other profile fields such as name or email. The backend does not maintain a separate user table for this flow.
Three different sources of "who the user is"
Server session (SIWE + JWT)
Cookie after POST /api/auth/verify
The address the user used to sign in. This is the only account for the API gateway.
Wallet (wagmi)
useAccount() and provider
The currently connected address in the extension; it can differ from the cookie address if the user switched accounts without signing in again.
Nado (trading)
SDK / linked signer
On-chain and engine-level logic; it does not replace server session verification for the THORNado HTTP API.
For features tied to being logged in to THORNado, rely on /api/auth/me or the session hook, and use the wallet for signatures and networks.
Client: where to get user data
useSession hook
useSession hookFile: client/src/hooks/useSession.js.
Performs
GET /api/auth/mewithcredentials: 'include'.A
401response is treated as "no session" and React Query returnsnull.Successful response:
address is always lowercase, matching the JWT on the server.
Usage example:
After sign-in or sign-out, the query cache should be refreshed. The project includes useInvalidateSession() for that purpose, which invalidates queryKey: ['session'].
Sign-in and sign-out in code
Sign-in (nonce -> signature -> verify)
client/src/lib/siweAuth.js - signInWithThornado
After success, invalidate the session query.
Sign-out
same file - logoutSession
POST /api/auth/logout + credentials: 'include'.
SIWE domain on the client: VITE_SIWE_DOMAIN or window.location.host. It must match SIWE_DOMAIN on the gateway.
Aligning session address and wallet address
It is useful to compare session?.address with the wagmi address explicitly. If they do not match, show a warning or suggest another SIWE flow. Example logic: client/src/pages/Account.jsx (sessionAddr, walletAddr, sessionMatchesWallet).
HTTP API (gateway, Go)
Implementation: gateway/main.go.
In development, the frontend calls http://localhost:5173/api/...; Vite proxies /api to the gateway at http://127.0.0.1:3001 - see client/vite.config.js.
Authentication endpoints
GET
/api/auth/nonce
Return a one-time nonce for SIWE. It lives for about 10 minutes.
POST
/api/auth/verify
Body: { "message": "<SIWE>", "signature": "0x..." } - verify the signature and issue a JWT in a cookie. Response: { "address": "0x..." }.
POST
/api/auth/logout
Clear the session cookie.
GET
/api/auth/me
Current user from cookie; 401 if not logged in. Response: { "address": "0x..." }.
Cookie: thornado_auth, HttpOnly, Path=/, SameSite=Lax, JWT lifetime by default is 24 hours.
New protected routes on the server
For endpoints that require the current user:
Attach middleware
requireAuth.Read the address from
c.Get("address").
There is no other data in the JWT right now. If a profile is needed later, it must be expanded separately.
Gateway environment variables
JWT_SECRET
JWT signing secret. Set it explicitly in production; otherwise the dev value from the code is used.
SIWE_DOMAIN
Domain in the SIWE message. It must match what the client sends. Default in code: localhost:5173.
SIWE_CHAIN_IDS or SIWE_CHAIN_ID
Allowed chain IDs for signing in, as a comma-separated list.
CORS_ORIGINS
Frontend origin for requests with cookies.
COOKIE_SECURE
1 or true sets the cookie Secure flag, which is required for HTTPS.
PORT
Gateway port. Defaults to 3001.
Short checklist for new code
Read the user from the server via
GET /api/auth/meoruseSession(), always withcredentials: 'include'.Do not confuse
session.addresswith the wallet address without checking.Do not treat a wagmi connection as equivalent to a server session.
For Nado operations, check the documentation and SDK separately. The server cookie is not injected into Nado requests automatically.
Was this helpful?

