{"record":{"id":"a1636adccf3112d4","repo":"immich-app/immich","slug":"error-backchannel-logout-token-validation-failed","errorCode":null,"errorMessage":"Error backchannel logout: token validation failed","messagePattern":"Error backchannel logout: token validation failed","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":107,"sourceCode":"      successful: true,\n      redirectUri: await this.getLogoutEndpoint(authType, oauthBearerToken),\n    };\n  }\n\n  async backchannelLogout(dto: OAuthBackchannelLogoutDto): Promise<void> {\n    const { oauth } = await this.getConfig({ withCache: false });\n    if (!oauth.enabled) {\n      throw new BadRequestException('Received backchannel logout request but OAuth is not enabled');\n    }\n\n    let claims;\n    try {\n      claims = await this.oauthRepository.validateLogoutToken(oauth, dto.logout_token);\n    } catch (error: Error | any) {\n      this.logger.error(`Error backchannel logout: ${error.message}`);\n      this.logger.error(error);\n\n      throw new BadRequestException('Error backchannel logout: token validation failed');\n    }\n\n    if (!claims) {\n      throw new BadRequestException('Invalid logout token: no claims found');\n    }\n\n    if (!claims.sub && !claims.sid) {\n      throw new BadRequestException('Invalid logout token: it must contain either a sub or a sid claim');\n    }\n\n    const deletedSessionIds = await this.sessionRepository.invalidateOAuth({\n      oauthSid: claims.sid,\n      oauthId: claims.sub,\n    });\n\n    for (const sessionId of deletedSessionIds) {\n      await this.eventRepository.emit('SessionDelete', { sessionId });\n    }","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/auth.service.ts#L89-L125","documentation":"backchannelLogout validates the IdP's logout_token via oauthRepository.validateLogoutToken. If validation throws (bad signature, wrong issuer/audience, expired token, missing required claims per OIDC Back-Channel Logout spec, key fetch failure), the error is logged and rethrown as this generic 400 so token details are not leaked to the caller.","triggerScenarios":"POST to the back-channel logout endpoint with a logout_token that fails validation: signed by an unexpected key, wrong iss/aud, expired, reused (replay), or malformed JWT.","commonSituations":"IdP rotated signing keys and Immich cached old JWKS; clock skew between Immich and the IdP making tokens appear expired; misconfigured issuer/audience/clientId in Immich's OAuth settings; the IdP sending an id_token instead of a logout_token; network/DNS failures fetching the IdP's discovery/JWKS document.","solutions":["Check the Immich server log line 'Error backchannel logout: <message>' for the underlying validation reason.","Verify Immich's OAuth issuer, clientId, and metadata URL match the IdP exactly; re-save the OAuth config to refresh cached discovery/JWKS.","Sync server clocks (NTP) between Immich and the identity provider.","Ensure the IdP is configured to send a proper logout_token (OIDC Back-Channel Logout), not an id_token or access token.","If the IdP rotated keys, restart Immich or wait for JWKS cache expiry and resend the logout request."],"exampleFix":"// diagnose by reading the underlying reason in server logs\n// immich log: \"Error backchannel logout: jwt issuer invalid. expected: https://idp.example.com\"\n// after: fix oauth.issuer in Immich settings to https://idp.example.com/realms/main","handlingStrategy":"try-catch","validationCode":"// decode the logout_token without verifying to sanity-check iss/aud before resending\nconst payload = JSON.parse(Buffer.from(logoutToken.split('.')[1], 'base64url').toString());\nif (payload.iss !== expectedIssuer) throw new Error('Issuer mismatch before sending');","typeGuard":null,"tryCatchPattern":"try { await api.oauthBackchannelLogout({ logout_token }); } catch (e) { if (e.status === 400) { readServerLogForCause(); refreshJwksOrConfig(); } }","preventionTips":["Keep issuer/clientId in Immich exactly matching the IdP","Run NTP time sync on both servers","Re-save OAuth config after IdP key rotation","Verify the IdP sends a logout_token, not an id_token"],"tags":["oauth","oidc","jwt","token-validation","immich"],"backgroundTag":"jwt-token-expired","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}