Skip to main content

Authentication

Wallet public keys are the primary user identity.

CredentialPrefixLifetime
Session JWTNone1 hour
API keytoda_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:

  1. parses the message and compares the address
  2. atomically consumes the stored nonce
  3. checks expiry
  4. compares the full message with the stored challenge
  5. 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_SECRET must 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.