gitroomhq/postiz-app · error · HttpException

{ error: 'invalid_client' }

Error message

{ error: 'invalid_client' }

What it means

Returned as HTTP 401 with OAuth error code invalid_client when exchanging an authorization code for tokens and the client_id does not match any registered OAuth application. This follows RFC 6749 section 5.2 client authentication failure semantics.

Source

Thrown at libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts:257

      codeExpiresAt,
      codeChallenge: pkce?.codeChallenge,
      codeChallengeMethod: pkce?.codeChallengeMethod,
      redirectUri: pkce?.redirectUri,
    });

    return code;
  }

  async exchangeCodeForToken(
    code: string,
    clientId: string,
    clientSecret?: string,
    codeVerifier?: string,
    redirectUri?: string
  ) {
    const app = await this._oauthRepository.getAppByClientId(clientId);
    if (!app) {
      throw new HttpException(
        { error: 'invalid_client' },
        HttpStatus.UNAUTHORIZED
      );
    }

    // Public clients (dynamic registration with token_endpoint_auth_method=none)
    // authenticate with PKCE instead of a client secret
    const isPublicClient = app.dynamic && app.tokenEndpointAuthMethod === 'none';
    if (!isPublicClient) {
      if (
        !clientSecret ||
        !app.clientSecret ||
        app.clientSecret !== AuthService.fixedEncryption(clientSecret)
      ) {
        throw new HttpException(
          { error: 'invalid_client' },
          HttpStatus.UNAUTHORIZED
        );

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Verify the client_id matches the OAuth app's clientId exactly (no extra spaces/quotes)
  2. Confirm the app exists in the same environment/database you're hitting
  3. If dynamically registered, re-register to obtain a fresh client_id and secret

Example fix

// before
const res = await fetch(tokenEndpoint, { body: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'postiz-prod-abc', code }) });
// after
const res = await fetch(tokenEndpoint, { body: new URLSearchParams({ grant_type: 'authorization_code', client_id: process.env.POSTIZ_CLIENT_ID!.trim(), code }) });
Defensive patterns

Strategy: validation

Validate before calling

if (!await clientExists(clientId)) { throw new Error('Unknown client_id — check the OAuth app settings'); }

Type guard

const hasClientId = (id?: string): id is string => !!id && id.trim().length > 0;

Try / catch

try { await exchange(code); } catch (e) { if (e?.response?.data?.error === 'invalid_client') { throw new Error('client_id not recognized on this server'); } throw e; }

Prevention

When it happens

Trigger: POST to the token endpoint with a grant_type=authorization_code request whose client_id is unknown, typo'd, deleted, or from a different environment.

Common situations: Copy-paste of the wrong client ID; app deleted or not yet propagated; using production client_id against staging server or vice versa; trailing whitespace in env var.

Related errors


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