gitroomhq/postiz-app · error · HttpException

{ error: 'invalid_grant', error_description: 'code_verifier

Error message

{ error: 'invalid_grant', error_description: 'code_verifier is required' }

What it means

Returned as HTTP 400 invalid_grant when the stored authorization record has a codeChallenge (PKCE was used at authorization) but the token exchange request omits the code_verifier parameter required by RFC 7636.

Source

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

    const encryptedCode = AuthService.fixedEncryption(code);
    const auth = await this._oauthRepository.findByCode(encryptedCode);
    if (!auth || auth.oauthAppId !== app.id) {
      throw new HttpException(
        { error: 'invalid_grant' },
        HttpStatus.BAD_REQUEST
      );
    }

    if (!auth.codeExpiresAt || new Date() > auth.codeExpiresAt) {
      throw new HttpException(
        { error: 'invalid_grant', error_description: 'Code has expired' },
        HttpStatus.BAD_REQUEST
      );
    }

    if (auth.codeChallenge) {
      if (!codeVerifier) {
        throw new HttpException(
          { error: 'invalid_grant', error_description: 'code_verifier is required' },
          HttpStatus.BAD_REQUEST
        );
      }
      const hashed = createHash('sha256').update(codeVerifier).digest('base64url');
      if (hashed !== auth.codeChallenge) {
        throw new HttpException(
          { error: 'invalid_grant', error_description: 'Invalid code_verifier' },
          HttpStatus.BAD_REQUEST
        );
      }
    }

    if (auth.redirectUri && redirectUri !== auth.redirectUri) {
      throw new HttpException(
        { error: 'invalid_grant', error_description: 'redirect_uri does not match the authorization request' },
        HttpStatus.BAD_REQUEST
      );

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Persist the code_verifier (cookie, session, DB) alongside the state from authorization through callback handling
  2. Include code_verifier in the token exchange request body
  3. Restart the flow if the verifier is unrecoverable

Example fix

// before
const tokens = await exchangeCode({ grant_type: 'authorization_code', client_id, code, redirect_uri });
// after
const tokens = await exchangeCode({ grant_type: 'authorization_code', client_id, code, redirect_uri, code_verifier: session.pkceVerifier });
Defensive patterns

Strategy: validation

Validate before calling

if (flowUsedPkce && !codeVerifier) {
  throw new Error('code_verifier missing — restore it from the authorize-step session');
}

Type guard

const hasVerifier = (v?: string | null): v is string => typeof v === 'string' && v.length >= 43;

Try / catch

try { return await exchange(body); } catch (e) { if (/code_verifier is required/.test(e?.response?.data?.error_description)) { return restartFlowWithPkce(); } throw e; }

Prevention

When it happens

Trigger: Authorization was done with a code_challenge, but the token request body lacks code_verifier; verifier lost between requests (page reload, server restart losing state, session lost).

Common situations: Storing the verifier in memory in an SPA that reloaded; storing it in a session that expired; splitting authorization and exchange across processes without passing the verifier; SDK configured for PKCE at authorize but not at exchange.

Related errors


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