immich-app/immich · error · UnauthorizedException
Invalid user token
Error message
Invalid user token
What it means
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').
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
Example fix
// before JWT_SECRET=new-secret # rotated, invalidates all sessions // after JWT_SECRET=<same stable secret across deployments>
Defensive patterns
Strategy: retry
Validate before calling
const payload = decodeJwt(token); if (payload.exp * 1000 < Date.now()) await relogin();
Type guard
const isJwt = (t: string) => t.split('.').length === 3; Try / catch
catch (e) { if (e.status === 401 && e.message === 'Invalid user token') { await refreshOrRelogin(); } } Prevention
- Keep JWT_SECRET stable across deployments
- Handle token refresh before expiry
- Re-login after logout/session revoke
When it happens
Trigger: Request with an Authorization bearer token that is expired, signed with a different secret, revoked, or whose session row was deleted.
Common situations: 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.
Related errors
- Authentication required
- Invalid JWT Token
- Missing JWT Token
- Unauthorized
- Elevated permission is required
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/f2a0d1aef69274f9.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:589
hasElevatedPermission = pinExpiresAt > now;
if (hasElevatedPermission && now.plus({ minutes: 5 }) > pinExpiresAt) {
await this.sessionRepository.update(session.id, {
pinExpiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate(),
});
}
}
return {
user: session.user,
session: {
id: session.id,
hasElevatedPermission,
},
};
}
throw new UnauthorizedException('Invalid user token');
}
async unlockSession(auth: AuthDto, dto: SessionUnlockDto): Promise<void> {
if (!auth.session) {
throw new BadRequestException('This endpoint can only be used with a session token');
}
const user = await this.userRepository.getForPinCode(auth.user.id);
this.validatePinCode(user, { pinCode: dto.pinCode });
await this.sessionRepository.update(auth.session.id, {
pinExpiresAt: DateTime.now().plus({ minutes: 15 }).toJSDate(),
});
}
async lockSession(auth: AuthDto): Promise<void> {
if (!auth.session) {
throw new BadRequestException('This endpoint can only be used with a session token');View on GitHub (pinned to f48d4b3321)