gitroomhq/postiz-app · error · Error

Token request failed: ${error}

Error message

Token request failed: ${error}

What it means

Generic OAuth provider base class: the provider's token endpoint returned a non-2xx while exchanging the authorization code for an access_token. The provider's response body is appended, typically containing error=invalid_client/invalid_grant/redirect_uri_mismatch.

Source

Thrown at apps/backend/src/services/auth/providers/oauth.provider.ts:71

    const { tokenUrl, clientId, clientSecret, frontendUrl } = this.getConfig();
    const response = await fetch(`${tokenUrl}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        Accept: 'application/json',
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: clientId,
        client_secret: clientSecret,
        code,
        redirect_uri: `${frontendUrl}/settings`,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Token request failed: ${error}`);
    }

    const { access_token } = await response.json();
    return access_token;
  }

  async getUser(access_token: string): Promise<{ email: string; id: string }> {
    const { userInfoUrl } = this.getConfig();
    const response = await fetch(`${userInfoUrl}`, {
      headers: {
        Authorization: `Bearer ${access_token}`,
        Accept: 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`User info request failed: ${error}`);

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Read the provider's error body in the message: invalid_client -> fix secret, invalid_grant -> fresh code, redirect_uri mismatch -> fix registration
  2. Verify FRONTEND_URL exactly matches the redirect URI registered with the provider (scheme, host, path /settings)
  3. Regenerate/re-verify CLIENT_ID and CLIENT_SECRET env values for that provider
  4. Ensure the code is exchanged exactly once, immediately after callback

Example fix

# before
FRONTEND_URL=http://localhost:4200

# after — must match the provider-registered callback exactly
FRONTEND_URL=https://app.example.com
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  const token = await provider.getToken(code);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Token request failed:')) {
    const body = e.message.slice('Token request failed:'.length);
    if (body.includes('redirect_uri')) fixRedirectUriRegistration();
    else if (body.includes('invalid_grant')) restartFlow();
    else throw new ConfigurationError('provider credentials', body);
  }
  throw e;
}

Prevention

When it happens

Trigger: Posting the code + client credentials to the provider's token URL with a wrong client_secret, an expired or already-used code, or a redirect_uri (`${frontendUrl}/settings`) that doesn't match what's registered on the provider app.

Common situations: Provider client secret rotated but env not updated; FRONTEND_URL env wrong or changed (http vs https, trailing slash) so redirect_uri mismatches; code used twice (double callback, retry logic); provider app in sandbox/test mode.

Related errors


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