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
- Re-run the client pairing/authorization flow so the client gets valid credentials and is added to authorizedClients.
- Verify config.authStrategy — if you intend open access locally, set it to AuthStrategy.NONE.
- Check that the client is sending its credentials (headers/cookies as the strategy expects) on every request.
- 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
- Persist client credentials after pairing and attach them to every request.
- Handle 401 centrally in the API client instead of per-endpoint.
- Document that switching authStrategy away from NONE invalidates unpaired clients.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of pear-devs/pear-desktop@1e2aac5706 (2026-08-27).
Data as JSON: /api/errors/20e2f7751855da7d.
Report an issue: GitHub.