{"record":{"id":"29b7eca421326c24","repo":"immich-app/immich","slug":"error-validating-jwt-logout-token","errorCode":null,"errorMessage":"Error validating JWT logout token","messagePattern":"Error validating JWT logout token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/repositories/oauth.repository.ts","lineNumber":191,"sourceCode":"      // eslint-disable-next-line unicorn/prefer-https\n      if (!events || !events['http://schemas.openid.net/event/backchannel-logout']) {\n        throw new Error('Missing backchannel-logout event claim');\n      }\n\n      // \"nonce\" must not be present\n      if (payload.nonce) {\n        throw new Error('Logout token must not contain a nonce');\n      }\n\n      return {\n        sub: payload.sub,\n        sid: payload.sid as string | undefined,\n      };\n    } catch (error: Error | any) {\n      this.logger.error(`Error validating JWT logout token: ${error.message}`);\n      this.logger.error(error);\n\n      throw new Error('Error validating JWT logout token', { cause: error });\n    }\n  }\n\n  private async getClient({\n    issuerUrl,\n    clientId,\n    clientSecret,\n    profileSigningAlgorithm,\n    signingAlgorithm,\n    tokenEndpointAuthMethod,\n    timeout,\n    allowInsecureRequests,\n  }: OAuthConfig) {\n    try {\n      return await discovery(\n        new URL(issuerUrl),\n        clientId,\n        {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/repositories/oauth.repository.ts#L173-L209","documentation":"validateLogoutToken verifies a back-channel logout JWT (signature, issuer, audience, expiry) and rethrows any verification failure as 'Error validating JWT logout token' with the original error as cause. It means the logout token received from the OIDC provider could not be trusted and the logout event is rejected.","triggerScenarios":"A back-channel logout POST delivers a JWT that fails jwtVerify: bad/expired signature, wrong issuer or audience, expired token, missing claims (events/sid/sub), or malformed token format.","commonSituations":"JWKS rotated and the old key is cached; provider issuer URL misconfigured in the app; clock skew marking the token invalid; provider sends an unexpected token type (e.g. access token instead of logout token).","solutions":["Inspect the logged underlying error message (`Error validating JWT logout token: <reason>`) for the specific verify failure","Verify the issuer URL and audience config match the provider exactly","Clear/refresh JWKS cache after provider key rotation","Confirm the provider is sending a proper logout token (with `events` claim) rather than a regular token"],"exampleFix":"// before\n// issuerUrl: 'https://provider.example.com'\n// after\n// issuerUrl: 'https://provider.example.com/'  // must match the iss claim exactly (trailing slash)","handlingStrategy":"try-catch","validationCode":"// decode without verification first to inspect claims\nconst { payload } = jwtDecode(logoutToken);\nif (!payload?.iss || payload.iss !== expectedIssuer) console.warn('Logout token issuer mismatch', payload?.iss);","typeGuard":"const isLogoutToken = (p: any): p is { events: object; sid?: string; sub?: string } =>\n  p && typeof p === 'object' && 'events' in p && Object.keys(p.events).some(k => k.includes('backchannel-logout'));","tryCatchPattern":"try {\n  await oauthRepo.validateLogoutToken(token);\n} catch (e) {\n  logger.error('Back-channel logout token rejected', { reason: (e as any).cause?.message });\n  return res.status(400).send(); // per OIDC back-channel logout spec\n}","preventionTips":["Monitor the cause message — it names the exact verify failure (expired, sig, iss, aud)","Refresh JWKS promptly after provider key rotation","NTP-synchronize the server to avoid clock-skew exp/nbf failures","Confirm with the provider that back-channel logout is enabled and sends proper logout tokens"],"tags":["jwt","oidc","logout","token-validation"],"backgroundTag":"jwt-token-expired","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}