{"record":{"id":"b90e219a791061cd","repo":"immich-app/immich","slug":"invalid-jwt-token","errorCode":null,"errorMessage":"Invalid JWT Token","messagePattern":"Invalid JWT Token","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/maintenance/maintenance-worker.service.ts","lineNumber":269,"sourceCode":"    } catch {\n      return this.getPublicStatus();\n    }\n  }\n\n  detectPriorInstall(): Promise<MaintenanceDetectInstallResponseDto> {\n    return detectPriorInstall(this.storageRepository);\n  }\n\n  async login(jwt?: string): Promise<MaintenanceAuthDto> {\n    if (!jwt) {\n      throw new UnauthorizedException('Missing JWT Token');\n    }\n\n    try {\n      const result = await jwtVerify<MaintenanceAuthDto>(jwt, new TextEncoder().encode(this.secret));\n      return result.payload;\n    } catch {\n      throw new UnauthorizedException('Invalid JWT Token');\n    }\n  }\n\n  async setAction(action: SetMaintenanceModeDto) {\n    this.setStatus({\n      active: true,\n      action: action.action,\n    });\n\n    await this.runAction(action);\n  }\n\n  async runAction(action: SetMaintenanceModeDto) {\n    switch (action.action) {\n      case MaintenanceAction.Start:\n      case MaintenanceAction.SelectDatabaseRestore: {\n        return;\n      }","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/maintenance/maintenance-worker.service.ts#L251-L287","documentation":"This UnauthorizedException is thrown when jwtVerify fails inside maintenanceWorkerService.login(): the JWT was supplied but its signature does not match the HMAC of this.worker.secret, it is malformed, or it is expired. It means the token cannot be trusted as a MaintenanceAuthDto payload.","triggerScenarios":"Calling login() with a JWT signed with a different secret (e.g. server regenerated its secret between sessions); token expired per its exp claim; corrupted/truncated token or one signed by the main Immich auth service instead of the maintenance worker; token string prefixed (e.g. 'Bearer x') and passed whole to jwtVerify.","commonSituations":"Restarting the maintenance server after it generated a new random secret while the browser still holds an old cookie/token; copying a token from another instance/environment; clock skew making a recently issued token appear expired; storing and replaying a token across version upgrades that changed signing details.","solutions":["Re-perform the maintenance login to get a freshly signed token and retry.","Do not include the 'Bearer ' prefix when passing the raw JWT string.","Ensure the request goes to the same server instance that issued the token (same secret).","If tokens keep failing after restarts, make the maintenance secret persistent/stable across restarts instead of regenerating per boot.","Check client/server clock synchronization if exp-based rejection is suspected."],"exampleFix":"// before\nconst token = authHeader; // \"Bearer eyJhbGci...\"\nawait worker.login(token); // Invalid JWT Token\n// after\nconst token = authHeader.replace(/^Bearer\\s+/i, '');\nawait worker.login(token);","handlingStrategy":"try-catch","validationCode":"if (!/^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$/.test(token)) {\n  throw new Error('Malformed JWT; re-login to obtain a valid maintenance token');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await worker.login(token);\n} catch (e) {\n  if (e instanceof UnauthorizedException && e.message === 'Invalid JWT Token') {\n    // clear stored token and re-authenticate\n    await worker.login(await obtainFreshToken());\n  } else throw e;\n}","preventionTips":["Re-login whenever the maintenance server restarts (secret may be regenerated).","Strip the 'Bearer ' prefix before passing the raw token.","Never reuse tokens across instances or environments.","Keep server clocks synchronized so exp validation behaves predictably.","Persist the maintenance secret if tokens must survive restarts."],"tags":["jwt","authentication","token-verification","unauthorized"],"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"}