gitroomhq/postiz-app · error · HttpException

code_challenge is required for this client

Error message

code_challenge is required for this client

What it means

Thrown when a public dynamic client (token_endpoint_auth_method=none, e.g. an SPA, CLI, or AI agent like an MCP client) sends an authorization request without a PKCE code_challenge. RFC 7636 PKCE is mandatory for public clients because they have no client secret to protect code interception.

Solutions

  1. Enable PKCE (S256) in your OAuth client library and retry the authorization request
  2. Verify code_challenge and code_challenge_method=S256 are present and URL-encoded in the authorize URL
  3. If the client can keep a secret, re-register with a confidential token_endpoint_auth_method instead

Example fix

// before
const client = new AuthorizationCode({
  clientId,
  redirectUri,
  tokenEndpointAuthMethod: 'none',
});
// after
const client = new AuthorizationCode({
  clientId,
  redirectUri,
  tokenEndpointAuthMethod: 'none',
});
const url = client.authorizeURL({
  scope: 'openid',
  code_challenge: await pkceChallengeFromVerifier(verifier),
  code_challenge_method: 'S256',
});
Defensive patterns

Strategy: validation

Validate before calling

if (client.tokenEndpointAuthMethod === 'none' && !codeChallenge) {
  codeChallenge = await computeS256Challenge(generateVerifier());
}

Type guard

const needsPkce = (app: {dynamic: boolean; tokenEndpointAuthMethod: string}) => app.dynamic && app.tokenEndpointAuthMethod === 'none';

Try / catch

try { await authorize(req); } catch (e) { if (e?.response?.status === 400 && /code_challenge/.test(e.message)) { return restartFlowWithPkce(); } throw e; }

Prevention

When it happens

Trigger: Authorization request for a dynamic client with tokenEndpointAuthMethod='none' and no code_challenge parameter in the query string, or the parameter name misspelled/mangled by your OAuth library config.

Common situations: Using a plain fetch/redirect flow instead of an OAuth library with PKCE enabled; library has PKCE disabled by default (e.g. some SDKs require usePkce: true / code_challenge_method config); switching a confidential client to public without updating the client code.

Related errors


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

Appendix: source

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

      redirectUri?: string;
      codeChallenge?: string;
      codeChallengeMethod?: string;
    }
  ) {
    const app = await this._oauthRepository.getAppByClientId(clientId);
    if (!app) {
      throw new HttpException('Invalid client_id', HttpStatus.BAD_REQUEST);
    }

    // Dynamically registered clients must use their registered redirect_uris
    // and PKCE; statically registered apps keep the existing lenient flow
    if (app.dynamic) {
      const registered: string[] = JSON.parse(app.redirectUris || '[]');
      if (!options?.redirectUri || !registered.includes(options.redirectUri)) {
        throw new HttpException('Invalid redirect_uri', HttpStatus.BAD_REQUEST);
      }
      if (app.tokenEndpointAuthMethod === 'none' && !options?.codeChallenge) {
        throw new HttpException(
          'code_challenge is required for this client',
          HttpStatus.BAD_REQUEST
        );
      }
      if (
        options?.codeChallenge &&
        options?.codeChallengeMethod &&
        options.codeChallengeMethod !== 'S256'
      ) {
        throw new HttpException(
          'Only the S256 code_challenge_method is supported',
          HttpStatus.BAD_REQUEST
        );
      }
    }

    return app;
  }

View on GitHub (pinned to 0f1647f749)