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

  1. Attach the shared-link identification header (x-immich-share-key / the share token) so auth.sharedLink is populated before calling login.
  2. Ensure the public/shared-link auth middleware runs on this route and isn't bypassed.
  3. 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

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

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/3dfa9207a9f1698e. Report an issue: GitHub.