gitroomhq/postiz-app · error · HttpException
{ error: 'invalid_grant', error_description: 'Code has expir
Error message
{ error: 'invalid_grant', error_description: 'Code has expired' } What it means
Returned as HTTP 400 invalid_grant when the authorization code's codeExpiresAt has passed or is missing. Authorization codes are short-lived (typically minutes) per RFC 6749, so delays between authorization and token exchange invalidate them.
Source
Thrown at libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts:289
) {
throw new HttpException(
{ error: 'invalid_client' },
HttpStatus.UNAUTHORIZED
);
}
}
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
);View on GitHub (pinned to 0f1647f749)
Solutions
- Complete the token exchange immediately upon receiving the callback; restart the flow if the code expired
- Verify server clock synchronization (NTP) if expiry seems premature
- If records have null codeExpiresAt from an older schema, re-authorize to create fresh records with expiry set
Defensive patterns
Strategy: fallback
Validate before calling
const age = Date.now() - codeIssuedAt;
if (age > CODE_TTL_MS) { return restartAuthorizationFlow(); } Type guard
const codeIsStillFresh = (issuedAt?: number): boolean => !!issuedAt && Date.now() - issuedAt < 5 * 60_000;
Try / catch
try { return await exchange(code); } catch (e) { if (e?.response?.data?.error_description === 'Code has expired') { return restartAuthorizationFlow(); } throw e; } Prevention
- Exchange the code immediately in the callback handler
- Keep server clocks NTP-synced
When it happens
Trigger: Exchanging a code more than the configured TTL after the user authorized; long user hesitation or queue latency before the exchange; server clock skew; record created without an expiry (legacy data with null codeExpiresAt).
Common situations: User sits on the consent/callback page before the app completes the exchange; paused debugger; background job processing the callback late; code saved and replayed later.
Related errors
- { error: 'invalid_grant' }
- { error: 'invalid_grant', error_description: 'code_verifier
- invalid_grant
- { error: 'invalid_client' }
- { error: 'invalid_grant', error_description: 'Invalid code_v
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/be0e2858968a6af1.
Report an issue: GitHub.