Authentication
Wallet public keys are the primary user identity.
| Credential | Prefix | Lifetime |
|---|---|---|
| Session JWT | None | 1 hour |
| API key | toda_ | Until revoked |
Both are sent as Authorization: Bearer <token>.
Sign-In With Solana
1. Request a challenge
POST /auth/challenge
{ "publicKey": "7xKX..." }
The server stores a 128-bit nonce and returns a structured SIWS message. The challenge expires after five minutes.
The signed message includes the domain, URI, chain ID, nonce, issue time, and expiry time.
2. Verify
POST /auth/verify
{ "publicKey": "7xKX...", "message": "<message>", "signature": "<base58>" }
The server:
- parses the message and compares the address
- atomically consumes the stored nonce
- checks expiry
- compares the full message with the stored challenge
- verifies the ed25519 signature
A successful first sign-in also creates the user's wrapped data key.
Nonce storage
Nonces are stored in Postgres so they remain single-use across restarts and multiple API instances. Concurrent verification attempts can consume a nonce only once.
Set SIWS_DOMAIN, SIWS_URI, and SIWS_CHAIN_ID to the production deployment values.
Session tokens
The core API issues an HS256 JWT with sub, pk, iat, and exp claims.
JWT_SECRETmust contain at least 32 characters.- Tokens expire after one hour.
- The core API does not support refresh or early revocation.
The extension API uses random stateful sessions. Token hashes are stored in extension_sessions, allowing logout and server-side revocation.
Disclosure of JWT_SECRET allows sessions to be forged for any user.
API keys
POST /keys
{ "label": "production" }
API keys contain 24 random bytes encoded as 48 hex characters after the toda_ prefix.
Only the SHA-256 hash and a display prefix are stored. The raw key is returned once.
GET /keys
DELETE /keys/:id
Revocation takes effect on the next request.
Bearer resolution
prefix is "toda_" -> hash and query api_keys
otherwise -> verify JWT
then -> load user and wrapped data key
Authorization
Memory queries are scoped to the authenticated userId. A request for another user's memory returns 404.
The core API has no roles or ACLs. Temporary sharing uses capability grants.
Verification access
Authentication controls plaintext reads. Commitment verification is public and does not require a toda session.