Authentication¶
Partly out of date
Rendered from ProjectInfo/client/authentication.md on docs/encryption-modes-spec. Its protocol content is still useful, but parts of it describe an older client or an earlier API. Where it disagrees with the code, the code wins — see the spec errata.
Registration¶
POST API_BASE/api/auth/register
Content-Type: application/json
{
"username": "alice",
"email": "[email protected]",
"password": "hunter2!!" // minimum 8 characters
}
Response 201:
{
"access_token": "eyJ...",
"refresh_token": "<raw>",
"user": { "id": "uuid", "username": "alice", "email": "[email protected]" }
}
Errors: 400 validation, 409 email already used
After registration, immediately run the E2EE key setup flow.
Login¶
POST API_BASE/api/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "hunter2!!"
}
Response 200: same shape as register
Errors: 401 invalid credentials
After login, run the post-auth key checks and check OTP count.
Token Refresh¶
Access tokens expire in 15 minutes. Refresh before expiry — do not wait for a 401.
POST API_BASE/api/auth/refresh
Content-Type: application/json
{ "refresh_token": "<raw-refresh-token>" }
Response 200: new access_token + new refresh_token
Errors: 401 — token invalid, expired, or already used
Critical behaviour:
- Refresh tokens are single-use — the old token is revoked on use
- Store the new refresh_token immediately before doing anything else
- If a refresh returns 401, redirect to login — the session is gone
- Fire the refresh at 13 minutes after the last access_token was issued (not at expiry)
Recommend a background timer or interceptor that tracks the token issue time from the JWT iat claim.
JWT Claims¶
{
"uid": "user-uuid",
"did": "device-id-string",
"bot": false,
"iss": "zipp-chat",
"iat": 1700000000,
"exp": 1700000900
}
The did (device ID) is required for all E2EE operations. Parse it from the JWT immediately after login/register and store it alongside your key material.
Token Storage¶
| Token | Where | Notes |
|---|---|---|
access_token |
In-memory only | Short-lived; never persist to disk |
refresh_token |
Secure local storage | 7-day lifetime; treat like a password |
Recommended storage per platform:
- Desktop (Tauri/Electron): OS credential store (keychain/libsecret)
- Mobile: iOS Keychain / Android Keystore
- Web: localStorage under key zipp_refresh_token (acceptable for web; prefer httpOnly cookies if server-rendered)
Post-Auth / Key Setup¶
Run this sequence after every login or registration. It is idempotent.
First time (no local key material)¶
1. Generate Ed25519 identity keypair (IK)
→ store private key securely; upload public key
2. Generate X25519 signed prekey (SPK):
a. Generate X25519 keypair
b. Sign SPK public key with IK private key (Ed25519 detached sig)
c. Assign SPK ID = 1 (increment on each future rotation)
3. Generate 100 X25519 one-time prekeys (OTPs):
- Unique monotonically-increasing IDs
- Store all private halves locally
4. POST API_BASE/api/keys/bundle
{
"identity_key": base64(IK_pub),
"signed_prekey": base64(SPK_pub),
"signed_prekey_id": 1,
"signed_prekey_signature": base64(sig)
}
5. POST API_BASE/api/keys/prekeys
{
"prekeys": [
{ "key_id": 1, "public_key": base64(otp_pub) },
... // 100 entries
]
}
On every login (keys already exist)¶
1. Parse `did` from JWT → confirm it matches stored device ID
If mismatch → this is a new device; run first-time setup instead
2. GET API_BASE/api/keys/prekeys/count
→ if count < 20: generate 80 new OTPs + POST /api/keys/prekeys
3. Check SPK age:
→ if signed prekey is > 7 days old:
a. Generate new X25519 keypair
b. Sign with IK, increment SPK ID
c. POST /api/keys/bundle (updates server record)
d. Retain old SPK locally until sessions using it ratchet forward
Fetching the Full Profile¶
The login/register response only returns { id, username, email }. Fetch the full profile for display:
Returns the full User object including display_name, avatar_url, status, etc.
Logout¶
Client-side only:
- Clear the
refresh_tokenfrom secure storage - Clear the
access_tokenfrom memory - Clear all locally cached channel keys and E2EE sessions if desired
- Navigate to login screen
The server automatically expires the refresh token after 7 days. There is no server-side logout endpoint.