auth: strip prompt=consent from initial authorize (auto-approve for connector)
Claude forces the consent screen with prompt=consent; the consent endpoint's signed-query round-trip is brittle. Simpler: drop prompt=consent from the initial unsigned authorize so skipConsent applies and no consent page is shown. Also pass raw query bytes to the login/consent handlers.
This commit is contained in:
+19
-2
@@ -77,6 +77,11 @@ async function handleConsent(req: IncomingMessage, res: ServerResponse, rawSearc
|
|||||||
|
|
||||||
const server = createServer((req, res) => {
|
const server = createServer((req, res) => {
|
||||||
const url = new URL(req.url ?? "/", "http://localhost");
|
const url = new URL(req.url ?? "/", "http://localhost");
|
||||||
|
// Raw query bytes (the URL API re-encodes .search, which breaks Better Auth's
|
||||||
|
// signed oauth_query check on /consent). Take the query straight from req.url.
|
||||||
|
const rawUrl = req.url ?? "/";
|
||||||
|
const qIndex = rawUrl.indexOf("?");
|
||||||
|
const rawSearch = qIndex >= 0 ? rawUrl.slice(qIndex) : "";
|
||||||
if (url.pathname !== "/healthz") {
|
if (url.pathname !== "/healthz") {
|
||||||
// Request-level trace (path only, no query — avoids logging codes/tokens).
|
// Request-level trace (path only, no query — avoids logging codes/tokens).
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -89,13 +94,25 @@ const server = createServer((req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (url.pathname === "/login" && req.method === "GET") {
|
if (url.pathname === "/login" && req.method === "GET") {
|
||||||
void startLogin(req, res, url.search);
|
void startLogin(req, res, rawSearch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (url.pathname === "/consent" && req.method === "GET") {
|
if (url.pathname === "/consent" && req.method === "GET") {
|
||||||
void handleConsent(req, res, url.search);
|
void handleConsent(req, res, rawSearch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Claude sends prompt=consent on the initial authorize, which forces the
|
||||||
|
// consent screen even with skipConsent. For a personal connector the login is
|
||||||
|
// the authorization, so strip it from the initial (unsigned) request — the
|
||||||
|
// signed resume after login has a `sig` and is left untouched.
|
||||||
|
if (
|
||||||
|
url.pathname === "/api/auth/oauth2/authorize" &&
|
||||||
|
!url.searchParams.has("sig") &&
|
||||||
|
url.searchParams.get("prompt") === "consent"
|
||||||
|
) {
|
||||||
|
url.searchParams.delete("prompt");
|
||||||
|
req.url = url.pathname + (url.searchParams.toString() ? `?${url.searchParams}` : "");
|
||||||
|
}
|
||||||
// Everything else -> Better Auth (async handler).
|
// Everything else -> Better Auth (async handler).
|
||||||
void handler(req, res);
|
void handler(req, res);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user