Skip to content

[Feature]: Scoped API tokens for automation, so users stop copying their session cookie into scripts #3040

Description

@cqnykamp

Before submitting

  • I searched existing issues and did not find a duplicate request.

Affected area

Developer experience / tooling

Layer

API (apps/api), Database / Prisma, Frontend (apps/app)

Problem

A user recently automated document creation on the site by copying their connect.sid cookie out of browser devtools and pasting it into a script. It worked — which is the problem. We offer no supported way to script against the API, so the only path available to a motivated user is to extract and reuse their browser session credential.

That credential is a poor fit for automation:

  • Full account authority. The session grants everything the user can do in the UI. A script that only needs to create documents can also delete content, change account settings, and read everything the user can read.
  • Long-lived. The session cookie is configured with maxAge: 365 * 24 * 60 * 60 * 1000 — one year (apps/api/src/index.ts). A cookie pasted into a script, committed to a repo, or shared with a collaborator stays valid for a very long time.
  • No revocation surface. There is no UI listing active sessions, so a user who suspects a leaked cookie has no way to invalidate it short of asking us to clear rows from the session store.
  • No attribution. Requests from a script are indistinguishable from requests from the browser in logs and rate limiting, so we can't scope, meter, or audit automated traffic.
  • Encourages worse habits. Once the pattern spreads, cookies get shared between people and end up in shell history, CI config, and Slack.

This is not hypothetical demand — people want to bulk-create and manage content programmatically, and they'll keep reaching for the cookie until there's something better.

Proposed solution

Add first-class, scoped API tokens as the supported automation path.

Token issuance and storage

  • New Prisma model (ApiToken): id, userId, label, hashed secret, scopes, createdAt, lastUsedAt, expiresAt, revokedAt.
  • Store only a hash of the secret (e.g. SHA-256 of a high-entropy random token); show the plaintext exactly once at creation.
  • Prefix tokens (doenet_pat_…) so they're greppable in secret scanners and recognizable in a paste.

Authentication

  • Accept Authorization: Bearer <token> as an alternative to the session cookie via a Passport strategy alongside the existing ones, resolving to the same UserInfoWithEmail shape so downstream handlers are unchanged.
  • Reject bearer-token requests to auth/session endpoints (login, magic link, account deletion) — tokens are for content operations, not identity changes.

Scopes

  • Start with a small, coarse set that covers real use: content:read, content:write, assignments:read, assignments:write. A token limited to content:write is a dramatically smaller blast radius than a session cookie.

Lifecycle and visibility

  • Account settings page to create, label, view (last-used timestamp, scopes), and revoke tokens.
  • Default expiry (90 days?) with explicit opt-in to longer.
  • Separate rate limits for token traffic, and tag tokened requests in logs.

Hardening the current path

  • Consider sameSite and shorter session lifetimes independently — a one-year session cookie is generous even for browser use.
  • Optionally reject requests that present a session cookie with no matching Origin/Referer from our own domains on write endpoints, which makes the copied-cookie approach stop working once a supported alternative exists. (Sequencing matters: ship tokens first, then tighten.)

Documentation

  • A short "Automating Doenet" doc showing token creation and a curl example, plus an explicit "never paste your session cookie into a script" note.

Alternatives considered

  • OAuth 2.0 / app authorization. The right answer if third-party applications ever act on behalf of Doenet users, but heavyweight for the actual need here — a user scripting against their own account. Personal tokens can be a stepping stone; the scope model carries over.
  • Do nothing / document the cookie approach. Cheapest, but blesses a credential with full authority, a one-year life, and no revocation.
  • Service accounts. Useful for institutional/LMS integrations, but doesn't help an individual author automating their own content.

Additional context

Relevant code: session config in apps/api/src/index.ts, Passport strategies and SessionUser shapes in apps/api/src/auth/. Session storage uses @quixo3/prisma-session-store, so revocation tooling would need to reach that table too.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Fields

    Priority

    Low

    Effort

    High

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions