immich-app/immich · warning · BadRequestException
Invalid logout token: no claims found
Error message
Invalid logout token: no claims found
What it means
Thrown by backchannelLogout after validateLogoutToken returns without throwing but yields a falsy claims object. Per the OIDC back-channel logout spec the token must carry identifiable claims; if the validated token has no usable claims the server rejects it with 400 BadRequest 'Invalid logout token: no claims found'. This is distinct from error 75 (validation threw) and error 77 (claims present but incomplete).
Source
Thrown at server/src/services/auth.service.ts:110
async backchannelLogout(dto: OAuthBackchannelLogoutDto): Promise<void> {
const { oauth } = await this.getConfig({ withCache: false });
if (!oauth.enabled) {
throw new BadRequestException('Received backchannel logout request but OAuth is not enabled');
}
let claims;
try {
claims = await this.oauthRepository.validateLogoutToken(oauth, dto.logout_token);
} catch (error: Error | any) {
this.logger.error(`Error backchannel logout: ${error.message}`);
this.logger.error(error);
throw new BadRequestException('Error backchannel logout: token validation failed');
}
if (!claims) {
throw new BadRequestException('Invalid logout token: no claims found');
}
if (!claims.sub && !claims.sid) {
throw new BadRequestException('Invalid logout token: it must contain either a sub or a sid claim');
}
const deletedSessionIds = await this.sessionRepository.invalidateOAuth({
oauthSid: claims.sid,
oauthId: claims.sub,
});
for (const sessionId of deletedSessionIds) {
await this.eventRepository.emit('SessionDelete', { sessionId });
}
}
async changePassword(auth: AuthDto, dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
const { password, newPassword } = dto;View on GitHub (pinned to 199723261c)
Solutions
- Decode the logout_token JWT locally (base64 of the payload) and inspect the claims.
- Have the IdP re-issue the logout token with a proper claims payload (sub/sid/events).
- Confirm the IdP implements OIDC back-channel logout (RFC) rather than a custom variant.
Defensive patterns
Strategy: try-catch
Validate before calling
// Inspect the logout_token payload before sending/forwarding.
function parseJwtPayload(token: string): any {
const part = token.split('.')[1];
return JSON.parse(Buffer.from(part, 'base64').toString('utf8'));
}
const claims = parseJwtPayload(logout_token);
if (!claims || Object.keys(claims).length === 0) {
throw new Error('Logout token has no claims; request a new one from the IdP.');
} Type guard
function hasClaims(claims: unknown): claims is Record<string, unknown> {
return !!claims && typeof claims === 'object' && Object.keys(claims as object).length > 0;
} Try / catch
try {
await api.post('/oauth/backchannel-logout', { logout_token });
} catch (e) {
if (e.response?.status === 400 && /no claims/i.test(e.response?.data?.message)) {
requestNewLogoutToken();
} else throw e;
} Prevention
- Decode the logout_token payload to confirm claims are present before forwarding.
- Configure the IdP to emit a standards-compliant back-channel logout token.
- Distinguish this from signature failures (error 75).
When it happens
Trigger: A logout_token that is structurally valid (signature/issuer ok) but whose payload decodes to null/empty claims; IdP issued a token with an empty payload section.
Common situations: Misconfigured IdP emitting minimal logout tokens; token corruption that drops the payload while preserving the header/signature; non-standard IdP behavior.
Related errors
- Invalid logout token: it must contain either a sub or a sid
- Error backchannel logout: token validation failed
- Received backchannel logout request but OAuth is not enabled
- Password login has been disabled
- OAuth is not enabled
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/0af8fd5bc77ca008.
Report an issue: GitHub.