actualbudget/actual · error
Invalid redirect URL
Invalid redirect URL
Error message
Invalid redirect URL
What it means
After a successful OpenID finalize, the server validates the redirect target with isValidRedirectUrl before issuing res.redirect. If the resulting URL is not a valid/allowed redirect (typically not relative to the server's own origin or absent), it responds 400 with reason 'Invalid redirect URL'. This guards against open-redirect attacks via crafted state or configuration.
Source
Thrown at packages/sync-server/src/app-openid.ts:108
const openIdConfig = JSON.parse(auth.extra_data);
res.send({ status: 'ok', data: { openId: openIdConfig } });
} catch {
res
.status(500)
.send({ status: 'error', reason: 'Invalid OpenID configuration' });
}
});
app.get('/callback', async (req, res) => {
const { error, url } = await loginWithOpenIdFinalize(req.query);
if (error) {
res.status(400).send({ status: 'error', reason: error });
return;
}
if (!isValidRedirectUrl(url)) {
res.status(400).send({ status: 'error', reason: 'Invalid redirect URL' });
return;
}
res.redirect(url);
});
app.use(errorMiddleware);
View on GitHub (pinned to d4334cb6e6)
Solutions
- Ensure the server's canonical URL/base origin configuration matches the URL users actually browse to
- Configure the reverse proxy to pass X-Forwarded-Host/X-Forwarded-Proto so the computed redirect matches the requested origin
- Start the login flow again from the app UI instead of a bookmarked/hand-built callback URL
- Inspect loginWithOpenIdFinalize/isValidRedirectUrl in app-openid.ts to confirm which origin is considered valid
Example fix
// before: server behind proxy computes http://internal:5006/redirect // after: pass forwarded headers proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme;
Defensive patterns
Strategy: validation
Validate before calling
function willRedirectValidate(url, serverOrigin) {
try {
const u = new URL(url, serverOrigin);
return u.origin === new URL(serverOrigin).origin;
} catch { return false; }
}
// ensure the browser origin matches the server's canonical URL before logging in Type guard
function isSameOriginRedirect(url, origin) {
try { return new URL(url, origin).origin === new URL(origin).origin; }
catch { return false; }
} Try / catch
if (res.status === 400) {
const body = await res.json();
if (body.reason === 'Invalid redirect URL') {
// recompute origin: open the app via the configured canonical URL and retry
window.location.href = canonicalServerUrl + '/openid/login';
}
} Prevention
- Access the server via its configured canonical URL only
- Forward X-Forwarded-Host/X-Forwarded-Proto on reverse proxies
- Never bookmark or hand-construct /openid/callback URLs
- Keep serverUrl/origin settings in sync with the public hostname
When it happens
Trigger: GET /openid/callback where the finalized `url` is undefined/null, an absolute URL to a foreign origin, or fails isValidRedirectUrl (e.g. missing the server's configured webhook/base origin).
Common situations: Server accessed via a different host/port/protocol than the configured canonical URL (reverse proxy without forwarded headers); state tampering; missing or wrong actual server URL configuration so the computed redirect points elsewhere.
Related errors
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/9de508a934db1e91.
Report an issue: GitHub.