immich-app/immich · error · ForbiddenException
Forbidden
Error message
Forbidden
What it means
SharedLinkService.login() requires that the incoming request carry a shared-link context (auth.sharedLink), normally supplied via the x-immich-share-key or shared-link token header. Without it, a bare ForbiddenException (HTTP 403) is thrown at shared-link.service.ts:29 before any password logic runs.
Source
Thrown at server/src/services/shared-link.service.ts:29
SharedLinkResponseDto,
SharedLinkSearchDto,
} from 'src/dtos/shared-link.dto';
import { Permission, SharedLinkType } from 'src/enum';
import { BaseService } from 'src/services/base.service';
import { findOrFail, getExternalDomain, OpenGraphTags } from 'src/utils/misc';
@Injectable()
export class SharedLinkService extends BaseService {
async getAll(auth: AuthDto, { id, albumId }: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
return this.sharedLinkRepository
.getAll({ userId: auth.user.id, id, albumId })
.then((links) => links.map((link) => mapSharedLink(link, { stripAssetMetadata: false })));
}
async login(auth: AuthDto, dto: SharedLinkLoginDto) {
if (!auth.sharedLink) {
throw new ForbiddenException();
}
const sharedLink = await this.findOrFail(auth.user.id, auth.sharedLink.id);
const { id, password } = sharedLink;
if (!password) {
throw new BadRequestException('Shared link is not password protected');
}
if (password !== dto.password) {
throw new UnauthorizedException('Invalid password');
}
return {
sharedLink: mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif }),
token: this.asToken({ id, password }),
};
}View on GitHub (pinned to 199723261c)
Solutions
- Attach the shared-link identification header (x-immich-share-key / the share token) so auth.sharedLink is populated before calling login.
- Ensure the public/shared-link auth middleware runs on this route and isn't bypassed.
- Verify any reverse proxy forwards custom x-immich-* headers unchanged.
Example fix
// before
fetch('/shared-links/me/login', { body: { password } });
// after
fetch('/shared-links/me/login', {
headers: { 'x-immich-share-key': sharedLinkKey },
body: { password },
}); Defensive patterns
Strategy: validation
Validate before calling
if (!shareKey) {
throw new Error('Shared-link key header is required to log in.');
}
await sharedLinkApi.login({ shareKey, password }); Type guard
const hasShareContext = (auth: AuthDto): boolean => !!auth.sharedLink;
Try / catch
try {
await sharedLinkApi.login(dto);
} catch (e) {
if (e instanceof ForbiddenException) {
promptForShareKey();
} else throw e;
} Prevention
- Always send the share identification header on public shared-link endpoints.
- Ensure reverse proxies forward x-immich-* headers.
When it happens
Trigger: Calling the shared-link login endpoint (auth flow for a password-protected shared link) without identifying which shared link you are logging into — i.e. missing the shared-link token/key header.
Common situations: Frontend routing directly to the password prompt without first resolving the share key, a reverse proxy stripping custom share headers, or a client using the wrong auth header for public shared-link access.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid share key
- Invalid password
- Password required
- Sync endpoints cannot be used with API keys
- Not authenticated with an API Key
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/3dfa9207a9f1698e.
Report an issue: GitHub.