Skip to main content

ADR 0004: Stream tokens for video playback

  • Status: accepted
  • Date: not recorded (LMS video studio work, November 2026)
  • Decision makers: the project owner, as recorded in the video studio plan

Context and problem statement​

The lesson page plays video with a browser-native <video src>. The API is on a different origin from the app, and a <video> request carries no Authorization header and none of the app's custom headers. The stream endpoint still has to serve only enrolled learners.

Decision drivers​

  • Do not ship an unauthenticated stream of enrolled content.
  • Do not break native playback and seeking.
  • Keep storage credentials on the server and avoid bucket CORS.
  • Reuse the platform's existing hand-rolled HS256 pattern.

Considered options​

  • Trust identity headers on the stream endpoint. Rejected: a <video> sends none.
  • Hand out a presigned object-storage URL. Rejected: expiring URLs under a stored reference, and it needs bucket CORS.
  • Proxy the stream through a Next.js route that injects headers. Rejected: routes up to 2 GB of media through the Node server for no security gain. Kept as a documented fallback.
  • Mint a short-lived, enrolment-scoped token on an authenticated call and carry it on the stream URL.

Decision outcome​

An authenticated stream-token call checks enrolment and returns an HS256 token holding the lesson id, the participant and an expiry. The stream path is public at the HTTP layer and verifies the token before serving a byte. Recordings use the same mechanism, keyed on the recording id. The token lasts 6 hours by default.

Consequences​

  • Good: native playback works; enrolment is checked at mint time with full authentication.
  • Bad: the token is in the URL and can appear in access logs. Its lifetime bounds a leaked URL.
  • Bad: the token is a bearer capability for 6 hours. It is not revoked if enrolment ends.

Confirmation​

LmsStreamTokenService verifies signature, expiry and lesson id, and fails with 403. The path is the only streaming entry in the public list in services/lms-service/src/main/resources/application.yml.

Sources​

.agents/tasks/lms-video-studio/plan.md, services/lms-service/src/main/java/com/rtih/startupos/lms/service/LmsStreamTokenService.java.