{"record":{"id":"f2a0d1aef69274f9","repo":"immich-app/immich","slug":"invalid-user-token","errorCode":null,"errorMessage":"Invalid user token","messagePattern":"Invalid user token","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":589,"sourceCode":"        hasElevatedPermission = pinExpiresAt > now;\n\n        if (hasElevatedPermission && now.plus({ minutes: 5 }) > pinExpiresAt) {\n          await this.sessionRepository.update(session.id, {\n            pinExpiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate(),\n          });\n        }\n      }\n\n      return {\n        user: session.user,\n        session: {\n          id: session.id,\n          hasElevatedPermission,\n        },\n      };\n    }\n\n    throw new UnauthorizedException('Invalid user token');\n  }\n\n  async unlockSession(auth: AuthDto, dto: SessionUnlockDto): Promise<void> {\n    if (!auth.session) {\n      throw new BadRequestException('This endpoint can only be used with a session token');\n    }\n\n    const user = await this.userRepository.getForPinCode(auth.user.id);\n    this.validatePinCode(user, { pinCode: dto.pinCode });\n\n    await this.sessionRepository.update(auth.session.id, {\n      pinExpiresAt: DateTime.now().plus({ minutes: 15 }).toJSDate(),\n    });\n  }\n\n  async lockSession(auth: AuthDto): Promise<void> {\n    if (!auth.session) {\n      throw new BadRequestException('This endpoint can only be used with a session token');","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/auth.service.ts#L571-L607","documentation":"Session-token authentication resolves the bearer JWT to a session and user. If the token cannot be verified, the session no longer exists, or the stored token hash no longer matches, validateSession throws UnauthorizedException('Invalid user token').","triggerScenarios":"Request with an Authorization bearer token that is expired, signed with a different secret, revoked, or whose session row was deleted.","commonSituations":"JWT_SECRET changed or differs between instances (e.g. after redeploy or multi-node setup without shared secret); user logged out / session cleared; token copied from another environment; clock skew making tokens appear expired.","solutions":["Log in again to obtain a fresh access token","Verify JWT_SECRET is identical and stable across restarts and all instances","Check the session was not deleted (logout, admin session revoke)","Sync server clocks / check token expiry handling"],"exampleFix":"// before\nJWT_SECRET=new-secret  # rotated, invalidates all sessions\n// after\nJWT_SECRET=<same stable secret across deployments>","handlingStrategy":"retry","validationCode":"const payload = decodeJwt(token); if (payload.exp * 1000 < Date.now()) await relogin();","typeGuard":"const isJwt = (t: string) => t.split('.').length === 3;","tryCatchPattern":"catch (e) { if (e.status === 401 && e.message === 'Invalid user token') { await refreshOrRelogin(); } }","preventionTips":["Keep JWT_SECRET stable across deployments","Handle token refresh before expiry","Re-login after logout/session revoke"],"tags":["jwt","session","unauthorized"],"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"}