HeyPuter/puter · error · HttpError
unauthorized
unauthorized
Error message
User required
What it means
Raised in the `refreshUser` step of the `userProtected` middleware chain when an actor exists but has no `user.id`. The userProtected gate protects sensitive browser-only operations (delete account, change password) and requires a fully authenticated user actor backed by a web session — anonymous, app-only, or access-token actors do not qualify.
Source
Thrown at src/backend/core/http/middleware/userProtected.ts:165
options: UserProtectedGateOptions = {},
): RequestHandler[] => {
const { config, userStore, oidcService, tokenService } = deps;
const allowTemp = !!options.allowTempUsers;
// 1. Session cookie only. Shared with the standalone cookie-only gate.
const requireSessionCookie = createSessionCookieGate(config);
// 2. Fresh user row (bypass cache to catch just-suspended accounts).
// `getById` doesn't take options; go through `getByProperty` with
// `{ force: true }` to force a primary read.
const refreshUser: RequestHandler = async (
req: Request,
_res: Response,
next: NextFunction,
) => {
const actor = req.actor;
if (!actor?.user?.id)
throw new HttpError(401, 'User required', {
legacyCode: 'unauthorized',
});
const user = await userStore.getByProperty('id', actor.user.id, {
force: true,
});
if (!user)
throw new HttpError(404, 'User not found', {
legacyCode: 'not_found',
});
if (user.suspended)
throw new HttpError(403, 'Account is suspended', {
legacyCode: 'account_suspended',
});
req.userProtected = { user };
next();
};
// 3. Password (bcrypt) OR valid OIDC revalidation cookie.View on GitHub (pinned to 908ec23eda)
Solutions
- Authenticate via the web session (browser login) before calling userProtected routes.
- Ensure the session cookie is sent with the request (credentials: 'include').
- Do not call these routes with API/app tokens — they are browser-session-only by design.
Example fix
// before
fetch('/user', { method: 'DELETE' }); // no cookie
// after
fetch('/user', { method: 'DELETE', credentials: 'include' }); Defensive patterns
Strategy: validation
Validate before calling
// Only call userProtected routes when a web session is present:
if (!hasSessionCookie()) { routeToLogin(); return; } Try / catch
try { await call({ credentials: 'include' }); }
catch (e) {
if (e.code === 'unauthorized') { routeToLogin(); return; }
throw e;
} Prevention
- Call userProtected routes with the web session cookie (credentials: 'include').
- Don't use API/app tokens for browser-session-only routes.
- Re-authenticate when the session expires.
When it happens
Trigger: Calling a userProtected route without a logged-in user session: expired session cookie, an API/app token used instead of the web session, or an anonymous request.
Common situations: Session expired between page load and the sensitive action; a script tried to call a browser-only route with an API token; the session cookie wasn't sent (cross-origin/CORS/credentials).
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/278c2539778decb3.
Report an issue: GitHub.