gitroomhq/postiz-app · error · HttpForbiddenException

Forbidden

Error message

Forbidden

What it means

The auth middleware requires a JWT on every authenticated request, read from the auth header or the auth cookie. If neither is present it immediately throws HttpForbiddenException (403 'Forbidden') before any JWT verification happens. This is the missing-credentials gate for the dashboard/API.

Source

Thrown at apps/backend/src/services/auth/auth.middleware.ts:37

          sameSite: 'none',
        }
      : {}),
    expires: new Date(0),
    maxAge: -1,
  });
  res.header('logout', 'true');
};

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(
    private _organizationService: OrganizationService,
    private _userService: UsersService
  ) {}
  async use(req: Request, res: Response, next: NextFunction) {
    const auth = req.headers.auth || req.cookies.auth;
    if (!auth) {
      throw new HttpForbiddenException();
    }
    try {
      // Verify the JWT signature only. Never trust authorization-relevant
      // claims (id, isSuperAdmin, activated) from the token body — always
      // re-resolve the user from the database using the id.
      const payload = AuthService.verifyJWT(auth) as User | null;
      const orgHeader = req.cookies.showorg || req.headers.showorg;

      if (!payload?.id) {
        throw new HttpForbiddenException();
      }

      let user = (await this._userService.getUserById(payload.id)) as User | null;

      if (!user) {
        throw new HttpForbiddenException();
      }

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Attach the JWT: pass it in the auth header or ensure the auth cookie is sent with the request
  2. For API clients, obtain the token via login and set the header on every request
  3. For browsers, verify cookies survive the cross-origin setup (credentials: 'include', correct SameSite/CORS config)
  4. Check that a proxy/gateway is not dropping the auth header or Cookie

Example fix

// before
await fetch(`${API}/posts`, { method: 'GET' }); // 403

// after
await fetch(`${API}/posts`, {
  method: 'GET',
  headers: { auth: token },        // or rely on credentials: 'include' for cookies
  credentials: 'include',
});
Defensive patterns

Strategy: validation

Validate before calling

const auth = getAuthCookie() ?? getStoredToken();
if (!auth) throw new Error('Missing credentials — login before calling authenticated routes');
await api.call({ headers: { auth } });

Type guard

const hasCredentials = (req: { headers: Record<string, unknown>; cookies: Record<string, unknown> }): boolean =>
  !!(req.headers?.auth || req.cookies?.auth);

Try / catch

try {
  await api.get('/protected');
} catch (e: any) {
  if (e?.status === 403 && !getAuthCookie()) {
    await redirectToLogin(); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Any request to a route guarded by this middleware without an Authorization/auth header and without an auth cookie — e.g. curl/API client forgetting the token, cookie lost after logout or expiry, cross-domain request where cookies are not sent (SameSite), or first request after session clear.

Common situations: Scripts calling authenticated endpoints without attaching the JWT; browser cookie blocked by SameSite/None misconfiguration when frontend and backend are on different origins; session cookie expired or cleared; reverse proxy stripping the auth header.

Understand the failure class

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/8cae0e1320d011af. Report an issue: GitHub.