gitroomhq/postiz-app · error · HttpException

Invalid redirect_uri

Error message

Invalid redirect_uri

What it means

Thrown during OAuth authorization request validation when the app was registered dynamically (RFC 7591) and the redirect_uri supplied in the request is missing or not in the client's registered redirectUris list. Dynamic clients use strict redirect_uri matching per the OAuth 2.0 spec, unlike statically registered Postiz apps which follow a lenient flow.

Source

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

  async validateAuthorizationRequest(
    clientId: string,
    options?: {
      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
        );
      }
    }

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Compare the exact redirect_uri string (scheme, host, port, path, query) against the JSON array in oauthApp.redirectUris for that clientId
  2. Re-register the client (dynamic registration endpoint) including the exact callback URL you redirect to
  3. If the app should use the lenient flow, verify it wasn't created via dynamic registration (app.dynamic flag)
  4. Check that redirect_uri is properly URL-encoded and not being stripped by your OAuth library

Example fix

// before
const url = `https://postiz.example.com/oauth/authorize?client_id=${id}`;
// after
const url = `https://postiz.example.com/oauth/authorize?client_id=${id}&redirect_uri=${encodeURIComponent('http://localhost:3000/callback')}`;
Defensive patterns

Strategy: validation

Validate before calling

const registered = app.redirectUris; // from dynamic registration response
if (!redirectUri || !registered.includes(redirectUri)) {
  throw new Error(`redirect_uri must be one of: ${registered.join(', ')}`);
}

Type guard

const isValidRedirectUri = (uri: string, registered: string[]): boolean => registered.includes(uri);

Try / catch

try { await authorize(req); } catch (e) { if (e instanceof HttpException && e.message === 'Invalid redirect_uri') { await reRegisterClient(); } throw e; }

Prevention

When it happens

Trigger: Calling the authorization endpoint with a dynamically registered client_id where the redirect_uri query param is omitted, has a different scheme/host/port/path, trailing slash, or was rotated after registration (stored as JSON in app.redirectUris).

Common situations: Dev server on http://localhost:3000 but registered http://127.0.0.1:3000; behind a proxy so the callback URL differs; client re-registered with new URIs but cached authorization URL; URL-encoding issues dropping the param.

Related errors


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