immich-app/immich · error · UnauthorizedException
Invalid JWT Token
Error message
Invalid JWT Token
What it means
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.
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.
Example fix
// before const token = authHeader; // "Bearer eyJhbGci..." await worker.login(token); // Invalid JWT Token // after const token = authHeader.replace(/^Bearer\s+/i, ''); await worker.login(token);
Defensive patterns
Strategy: try-catch
Validate before calling
if (!/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/.test(token)) {
throw new Error('Malformed JWT; re-login to obtain a valid maintenance token');
} Try / catch
try {
await worker.login(token);
} catch (e) {
if (e instanceof UnauthorizedException && e.message === 'Invalid JWT Token') {
// clear stored token and re-authenticate
await worker.login(await obtainFreshToken());
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/b90e219a791061cd.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/maintenance/maintenance-worker.service.ts:269
} catch {
return this.getPublicStatus();
}
}
detectPriorInstall(): Promise<MaintenanceDetectInstallResponseDto> {
return detectPriorInstall(this.storageRepository);
}
async login(jwt?: string): Promise<MaintenanceAuthDto> {
if (!jwt) {
throw new UnauthorizedException('Missing JWT Token');
}
try {
const result = await jwtVerify<MaintenanceAuthDto>(jwt, new TextEncoder().encode(this.secret));
return result.payload;
} catch {
throw new UnauthorizedException('Invalid JWT Token');
}
}
async setAction(action: SetMaintenanceModeDto) {
this.setStatus({
active: true,
action: action.action,
});
await this.runAction(action);
}
async runAction(action: SetMaintenanceModeDto) {
switch (action.action) {
case MaintenanceAction.Start:
case MaintenanceAction.SelectDatabaseRestore: {
return;
}View on GitHub (pinned to e55ac299a4)