gitroomhq/postiz-app · error · HttpException
invalid_grant
invalid_grant
Error message
{ error: 'invalid_grant', error_description: 'redirect_uri does not match the authorization request' } What it means
Returned as HTTP 400 invalid_grant when the authorization record has a redirectUri and the redirect_uri sent at token exchange differs from it. RFC 6749 requires redirect_uri at exchange to match exactly when one was used at authorization.
Source
Thrown at libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts:312
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
);
}
const token = 'pos_' + makeId(40);
const encryptedToken = AuthService.fixedEncryption(token);
const {
organizationId,
organization: { paymentId },
} = await this._oauthRepository.exchangeCodeForToken(
auth.id,
encryptedToken
);
return {
id: organizationId,
cus: paymentId,View on GitHub (pinned to 0f1647f749)
Solutions
- Send the identical redirect_uri string at both authorization and exchange
- Centralize the callback URL in one config value used by both calls
- Check for trailing slashes, scheme (http vs https), port, and encoding differences
Example fix
// before
authorizeURL({ redirect_uri: 'http://localhost:3000/cb' });
exchangeCode({ redirect_uri: 'http://localhost:3000/callback' });
// after
const redirectUri = process.env.REDIRECT_URI!;
authorizeURL({ redirect_uri: redirectUri });
exchangeCode({ redirect_uri: redirectUri }); Defensive patterns
Strategy: validation
Validate before calling
if (authRedirectUri && exchangeRedirectUri !== authRedirectUri) {
throw new Error('redirect_uri mismatch between authorize and exchange');
} Type guard
const redirectUrisMatch = (a?: string, b?: string): boolean => !a || a === b;
Try / catch
try { return await exchange(body); } catch (e) { if (/redirect_uri/.test(e?.response?.data?.error_description)) { body.redirect_uri = savedRedirectUri; return exchange(body); } throw e; } Prevention
- Single source of truth for the callback URL
- Beware proxies rewriting scheme/host — configure forwarded headers correctly
When it happens
Trigger: Including a different redirect_uri in the token request than in the authorize request; omitting it inconsistently; differing trailing slash, port, or query parameter; proxy rewrites changing the externally visible URL.
Common situations: App running on a different port/host between the two steps; load balancer terminating SSL so http vs https differs; hardcoded redirect in one place and env-driven in another; URL normalization by a framework.
Related errors
- { error: 'invalid_grant' }
- { error: 'invalid_grant', error_description: 'Code has expir
- { error: 'invalid_grant', error_description: 'code_verifier
- Invalid redirect_uri
- { error: 'invalid_client' }
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/9d0a82ff31cadbfd.
Report an issue: GitHub.