pear-devs/pear-desktop · warning

Unauthorized

Error message

Unauthorized

What it means

'Unauthorized' is the HTTP 401 body returned by the api-server's auth middleware when a client's request is not authorized. Authorization passes only when the configured authStrategy is NONE, or when client verification succeeded (result.success) AND the client's id is in config.authorizedClients. Otherwise the middleware sets status 401, returns 'Unauthorized', and short-circuits before the route handler runs.

Source

Thrown at src/plugins/api-server/backend/main.ts:122

          alg: 'HS256',
        })(ctx, next);
      }
      return await next();
    };
    this.app.use('/api/*', jwtGuard);
    this.app.use('/api/*', async (ctx, next) => {
      if (ctx.req.path.endsWith(`${API_VERSION}/ws`)) {
        return await next();
      }

      const result = await JWTPayloadSchema.spa(await ctx.get('jwtPayload'));
      const config = await backendCtx.getConfig();

      const isAuthorized =
        config.authStrategy === AuthStrategy.NONE ||
        (result.success && config.authorizedClients.includes(result.data.id));
      if (!isAuthorized) {
        ctx.status(401);
        return ctx.body('Unauthorized');
      }

      return await next();
    });

    // routes
    registerControl(
      this.app,
      backendCtx,
      () => this.songInfo,
      () => this.currentRepeatMode,
      () =>
        backendCtx.window.webContents.executeJavaScript(
          'document.querySelector("#like-button-renderer")?.likeStatus',
        ) as Promise<LikeType>,
      () => this.volumeState,
    );

View on GitHub (pinned to 1e2aac5706)

Solutions

  1. Re-run the client pairing/authorization flow so the client gets valid credentials and is added to authorizedClients.
  2. Verify config.authStrategy — if you intend open access locally, set it to AuthStrategy.NONE.
  3. Check that the client is sending its credentials (headers/cookies as the strategy expects) on every request.
  4. Inspect authorizedClients in the persisted config and add the client's id if verification succeeds but membership is missing.

Example fix

// before (client request without credentials)
await fetch('http://localhost:10768/api/songs'); // 401 Unauthorized

// after (complete pairing first, then send credentials)
// pair the client once, e.g. via the auth/register endpoint, then:
await fetch('http://localhost:10768/api/songs', {
  headers: { Authorization: `Bearer ${clientToken}` },
});
Defensive patterns

Strategy: validation

Validate before calling

// client-side: verify credentials are configured before making API calls
if (authStrategy !== 'none' && !clientCredentials) {
  throw new Error('Not paired with server — run registration first');
}
await api.getSongs();

Type guard

const isUnauthorized = (res: Response): boolean => res.status === 401;

Try / catch

const res = await fetch(url, opts);
if (res.status === 401) {
  // re-pair or prompt user, then retry once
  await reauthorize();
  return fetch(url, opts);
}

Prevention

When it happens

Trigger: Any API request (GET/POST on any route) where the strategy is not NONE and either client verification failed (invalid/expired credentials, unknown client id) or the verified client id is not present in authorizedClients in the config.

Common situations: Client pairing not completed or pairing dialog rejected, client credentials deleted from authorizedClients, authStrategy changed from NONE to a real strategy without re-pairing clients, stale credentials after server restart, or a third-party tool hitting the API without credentials.

Understand the failure class

Related errors


AI-assisted analysis of pear-devs/pear-desktop@1e2aac5706 (2026-08-27). Data as JSON: /api/errors/20e2f7751855da7d. Report an issue: GitHub.