HeyPuter/puter · error · HttpError

token_auth_failed

token_auth_failed

Error message

Token authentication failed

What it means

Thrown by tokenRead (GET /read with ?token=) when the request has no `token` query parameter at all. This endpoint authenticates a token by hand instead of going through the normal auth gate chain, so the very first check is that a token string was supplied. Without it the request is anonymous and cannot be authorized to read any file.

Source

Thrown at src/backend/controllers/fs/LegacyFSController.ts:1207

        if (download.lastModified)
            res.setHeader('Last-Modified', download.lastModified.toUTCString());
        res.setHeader(
            'Content-Disposition',
            `inline; filename="${encodeURIComponent(entry.name)}"`,
        );
        res.status(range ? 206 : 200);

        download.body.on('error', (err) => {
            res.destroy(err);
        });
        download.body.pipe(res);
    };

    tokenRead = async (req: Request, res: Response): Promise<void> => {
        const query = asRecord(req.query);
        const accessToken = getString(query, 'token');
        if (!accessToken) {
            throw new HttpError(401, 'Token authentication failed', {
                legacyCode: 'token_auth_failed',
            });
        }

        const actor =
            await this.services.auth.authenticateFromToken(accessToken);
        if (!isAccessTokenActor(actor)) {
            throw new HttpError(401, 'Token authentication failed', {
                legacyCode: 'token_auth_failed',
            });
        }

        // This endpoint authenticates the token by hand and never runs the
        // route gate chain, so the suspension and pending-verification checks
        // that guard every other authenticated FS route have to run here.
        assertNotSuspended(actor!.user);
        assertVerifiedAccount(actor!.user);

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Append the access token as a query parameter: GET /read?token=<your_access_token>&uid=<uid>.
  2. If you obtained a signed URL from /sign or /open_item, use the returned read_url/token field verbatim rather than reconstructing it.
  3. Confirm the token is not being dropped by URL encoding or a redirecting gateway that strips unknown params.

Example fix

// before
fetch('/read?uid=' + uid)
// after
fetch('/read?uid=' + uid + '&token=' + encodeURIComponent(accessToken))
Defensive patterns

Strategy: validation

Validate before calling

// client-side guard before building the /read?token= URL
function buildTokenReadUrl(uid, token) {
  if (typeof token !== 'string' || token.trim().length === 0) {
    throw new Error('Cannot call /read without an access token');
  }
  const params = new URLSearchParams({ uid, token: token.trim() });
  return `/read?${params.toString()}`;
}

Type guard

// narrow a candidate token to a non-empty string
/** @param {unknown} t
 * @returns {t is string}
 */
function isNonEmptyToken(t) {
  return typeof t === 'string' && t.trim().length > 0;
}

Try / catch

try {
  const res = await fetch(buildTokenReadUrl(uid, token));
  if (res.status === 401) { /* re-auth, then retry once */ }
} catch (e) { /* network error, not the 401 */ }

Prevention

When it happens

Trigger: Calling GET /read?uid=<uid> (or /read with a path) while omitting the `token` query param, or passing `?token=` (empty string). getString(query,'token') returns '' and the truthiness check fails.

Common situations: A puter.js readWithToken/signed-read flow that forgot to append the token; a bookmarked /read URL whose token query got stripped by a redirect or proxy; building the URL manually and missing the key.

Understand the failure class

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/75659e6ac0109ec6. Report an issue: GitHub.