Auth model for Kobo reading-services endpoints (annotations) #23

Open
opened 2026-04-20 20:56:27 +02:00 by arne · 0 comments
Owner

Future implementation spec for annotation sync over the Kobo reading_services_host endpoints will need an auth model. Documenting the proposed approach here so the spec can reference it.

Observed from the annotations research spike

The Kobo device sends annotation traffic with a Kobo-cloud-issued Bearer JWT in the Authorization header. Example claims (captured via nc :5001):

{
  "iss": "https://auth.kobobooks.com",
  "aud": ["https://auth.kobobooks.com/resources", "profileauth", "public_api"],
  "client_id": "nickel",
  "kobo_user_id": "7081525b-05f9-4cbf-91e8-c93f936d17f6",
  "scope": ["openid", "profile", "kobo_profile", "public_api_authenticated", ...],
  "nbf": 1776709922,
  "exp": 1776713522
}

The JWT itself rotates (~1 hour expiry). The kobo_user_id claim inside it is stable for the account.

Proposed auth model

Trust-on-first-use on the kobo_user_id claim, mirroring the existing first-login-owner pattern (#19):

  1. On first request to a reading-services endpoint, base64-decode the JWT payload (no signature verification — we can't, Kobo holds the signing key).
  2. Extract kobo_user_id.
  3. Persist as the server's bound Kobo user.
  4. On subsequent requests, decode again and reject any JWT whose kobo_user_id differs from the bound value.

Surface: /api/UserStorage/*, /api/v3/content/*, plus whatever else the annotations spec ends up owning.

Why not validate the JWT signature

The JWT is signed with a Kobo-cloud key. We're a self-hosted replacement for Kobo cloud; we intentionally don't have Kobo's key and can't check it. TOFU on the user ID is the closest available analogue.

  • Annotations research spike findings: docs/superpowers/specs/2026-04-20-kobo-annotations-spike-design.md (pending writeup at docs/kobo-annotations-spike-findings.md)
  • First-login owner pattern: #19
Future implementation spec for annotation sync over the Kobo `reading_services_host` endpoints will need an auth model. Documenting the proposed approach here so the spec can reference it. ## Observed from the annotations research spike The Kobo device sends annotation traffic with a Kobo-cloud-issued Bearer JWT in the `Authorization` header. Example claims (captured via `nc :5001`): ```json { "iss": "https://auth.kobobooks.com", "aud": ["https://auth.kobobooks.com/resources", "profileauth", "public_api"], "client_id": "nickel", "kobo_user_id": "7081525b-05f9-4cbf-91e8-c93f936d17f6", "scope": ["openid", "profile", "kobo_profile", "public_api_authenticated", ...], "nbf": 1776709922, "exp": 1776713522 } ``` The JWT itself rotates (~1 hour expiry). The `kobo_user_id` claim inside it is stable for the account. ## Proposed auth model Trust-on-first-use on the `kobo_user_id` claim, mirroring the existing first-login-owner pattern (#19): 1. On first request to a reading-services endpoint, base64-decode the JWT payload (no signature verification — we can't, Kobo holds the signing key). 2. Extract `kobo_user_id`. 3. Persist as the server's bound Kobo user. 4. On subsequent requests, decode again and reject any JWT whose `kobo_user_id` differs from the bound value. Surface: `/api/UserStorage/*`, `/api/v3/content/*`, plus whatever else the annotations spec ends up owning. ## Why not validate the JWT signature The JWT is signed with a Kobo-cloud key. We're a self-hosted replacement for Kobo cloud; we intentionally don't have Kobo's key and can't check it. TOFU on the user ID is the closest available analogue. ## Related - Annotations research spike findings: `docs/superpowers/specs/2026-04-20-kobo-annotations-spike-design.md` (pending writeup at `docs/kobo-annotations-spike-findings.md`) - First-login owner pattern: #19
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
arne/books#23
No description provided.