immich-app/immich · error · UnauthorizedException
Invalid user token
Error message
Invalid user token
What it means
Thrown at the tail of AuthService.validateSession when no valid session could be built from the supplied token+headers. The token is SHA-256 hashed and matched to a session record; if the session/user cannot be resolved, all earlier return paths are skipped and execution reaches this catch-all.
Source
Thrown at server/src/services/auth.service.ts:585
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 199723261c)
Solutions
- Have the client re-authenticate (log in again) to obtain a fresh session token.
- Verify the token is transmitted intact (no truncation/encoding by a reverse proxy or cookie size limit).
- Confirm the sessions table is intact on this instance (not wiped by a restore/migration).
- Treat the 401 as a trigger to clear the local session and redirect to login.
Example fix
// before
const auth = await authService.validateSession(token, headers);
// after
let auth: AuthDto;
try {
auth = await authService.validateSession(token, headers);
} catch (e) {
if (e instanceof UnauthorizedException) {
// session is gone — force a clean re-login rather than retrying the dead token
await sessionStore.clear();
throw new UnauthorizedException('Session expired, please log in again');
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!token || token.length < 32) {
throw new UnauthorizedException('Session token is missing');
} Type guard
function isSessionToken(v: unknown): v is string {
return typeof v === 'string' && /^[A-Za-z0-9+/=]{32,}$/.test(v);
} Try / catch
try {
auth = await authService.validateSession(token, headers);
} catch (e) {
if (e instanceof UnauthorizedException) {
await sessionStore.clear(); // dead token — purge locally
throw new UnauthorizedException('Session expired; please log in again');
}
throw e;
} Prevention
- Clear stored tokens immediately on a 401 rather than retrying the same dead token.
- Invalidate sessions server-side on password change so stale tokens cannot linger.
- Ensure reverse proxies do not truncate large cookies.
When it happens
Trigger: A request authenticated with a session cookie/bearer token that does not hash to an existing session: logged-out token, session invalidated server-side (e.g. password change / admin forced logout), token from another instance, or a malformed token.
Common situations: Stale browser cookie after the user changed their password or an admin invalidated sessions; token copied from a different deployment; cookie truncated by a proxy; session expired and was reaped.
Related errors
- Unauthorized
- Password login has been disabled
- Incorrect email or password
- Invalid logout token: it must contain either a sub or a sid
- Authentication required
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/f2a0d1aef69274f9.
Report an issue: GitHub.