fix(auth): harden logout redirect with origin validation and protocol check

Add origin allowlist validation in the OAuth logout handler (matching
the existing start/refresh pattern) and validate the logoutUrl protocol
on the frontend before redirecting. Also replace inline type annotation
with the named OAuthAuthenticatorLogoutResult type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
This commit is contained in:
Jack Palmer
2026-04-01 08:55:12 +01:00
committed by Jonathan Roebuck
parent a07f0196e2
commit 3532be4763
2 changed files with 20 additions and 7 deletions
@@ -39,7 +39,11 @@ import {
ProfileTransform,
SignInResolver,
} from '../types';
import { OAuthAuthenticator, OAuthAuthenticatorResult } from './types';
import {
OAuthAuthenticator,
OAuthAuthenticatorLogoutResult,
OAuthAuthenticatorResult,
} from './types';
import { Config, readDurationFromConfig } from '@backstage/config';
import { CookieScopeManager } from './CookieScopeManager';
@@ -280,7 +284,12 @@ export function createOAuthRouteHandlers<TProfile>(
throw new AuthenticationError('Invalid X-Requested-With header');
}
let logoutResult: void | { logoutUrl?: string } = undefined;
const origin = req.get('origin');
if (origin && !isOriginAllowed(origin)) {
throw new NotAllowedError(`Origin '${origin}' is not allowed`);
}
let logoutResult: void | OAuthAuthenticatorLogoutResult = undefined;
if (authenticator.logout) {
const refreshToken = cookieManager.getRefreshToken(req);
logoutResult = await authenticator.logout(
@@ -290,7 +299,7 @@ export function createOAuthRouteHandlers<TProfile>(
}
// remove refresh token cookie if it is set
cookieManager.removeRefreshToken(res, req.get('origin'));
cookieManager.removeRefreshToken(res, origin);
// remove persisted scopes
await scopeManager.clear(req);