Tutorial

Authentication

Every request to the API carries a session token signed with ML-DSA-87 (FIPS 204). This page covers obtaining one and using it.

There are two distinct credentials in the platform, and it is worth separating them up front:

CredentialAnswersLifetime
Session tokenWho are you?24h, whole account
UCAN tokenWhat may you do to this one resource?Minutes to days, one grant

A session token authenticates you. A UCAN grants a narrow, delegable capability over a specific resource. You always need the first; you use the second to share.

1. Register

bash
curl -X POST https://api.ekayana.com/api/signup \
  -H "Content-Type: application/json" \
  -d '{
    "username": "ada",
    "email": "ada@lab.example",
    "password": "correct-horse-battery"
  }'

username must be 3-50 characters, email must parse as an address, and password has a minimum length of 8 plus a complexity rule. An optional name field carries a display name.

json
{ "token": "eyJhbGciOi...<payload>...<signature>" }

2. Sign in

bash
curl -X POST https://api.ekayana.com/api/signin \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@lab.example", "password": "correct-horse-battery" }'

Returns the same { "token": ... } shape. /api/signup and /api/signin are the only unauthenticated write endpoints.

3. Call an authenticated endpoint

bash
curl https://api.ekayana.com/api/users/me \
  -H "Authorization: Bearer $TOKEN"
json
{ "id": 42, "username": "ada", "email": "", "did": "did:bio:user:42" }

The header must be exactly Authorization: Bearer <token>. A missing header, a malformed one, or a token that fails verification all return an auth error before the handler runs.

Note: /api/users/me returns an empty email - the field is not carried in the token claims. Query the profile directly if you need it.

To find collaborators to share with, GET /api/users/search?q=<query> matches username or email (minimum 2 characters, capped at 10 results) and returns each user's did, which is what the sharing flow consumes.

4. Token structure

A successful signup or signin returns a session token signed with ML-DSA-87 (FIPS 204) - the same post-quantum suite used for capability tokens. The structure is JWS-shaped:

code
base64url(header) . base64url(claims) . base64url(signature)
json
// header
{ "alg": "ML-DSA-87", "typ": "JWT", "nonce": "..." }

// claims
{ "sub": "42", "iat": 1753228800, "exp": 1753315200, "nonce": "..." }

Present it as a bearer credential on every authenticated request:

bash
curl https://api.ekayana.com/api/users/me \
  -H "Authorization: Bearer <token>"

How verification works

The entire signature is carried in the token and checked against the service public key before any claim is read. Two properties follow:

  • No secret is needed to verify. Read-only replicas and external auditors can validate a token without the ability to mint one.
  • Claims are tamper evident. Editing sub to impersonate another user invalidates the signature, so the token is rejected before the claim is trusted.
Token lifetime defaults to 24 hours and is configurable via JWT_EXPIRATION_SECS. Expiry is enforced on every request; there is no refresh window, so clients re-authenticate when a token lapses.

See Post-Quantum Security for the signing identity and key management.