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
- Persist the code_verifier (cookie, session, DB) alongside the state from authorization through callback handling
- Include code_verifier in the token exchange request body
- 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
- Persist the verifier server-side keyed by state, not in SPA memory
- Use one library call that owns both authorize and exchange steps
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
- { error: 'invalid_grant' }
- { error: 'invalid_grant', error_description: 'Code has expir
- { error: 'invalid_grant', error_description: 'Invalid code_v
- invalid_grant
- code_challenge is required for this client
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/3b5f14a71cf4c6fb.
Report an issue: GitHub.