immich-app/immich · error · UnauthorizedException

Password required

Error message

Password required

What it means

SharedLink.getMine returns the caller's own shared link but enforces that the request's auth token matches the token derived from the link's password. If the link has a password and the presented auth token is not the expected password token, access is refused with UnauthorizedException('Password required').

Solutions

  1. Complete the shared-link password login first, then use the returned token for getMine.
  2. If the link password changed, re-authenticate with the new password to mint a fresh token.
  3. For passwordless links no token exchange is needed — verify the right link/key is used.

Example fix

// before
const link = await api.getMySharedLink(shareKeyToken); // token predates password
// after
const { token } = await api.sharedLinkLogin(shareKey, { password });
const link = await api.getMySharedLink(token);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await api.getMySharedLink(token);
} catch (e) {
  if (e.status === 401 && e.message === 'Password required') {
    const { token: fresh } = await api.sharedLinkLogin(shareKey, { password });
    return await api.getMySharedLink(fresh);
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /shared-links/me (getMine) authenticated with auth.sharedLink credentials whose token is not in authTokens for a password-protected link — e.g. accessing the link metadata without first completing the password login.

Common situations: Client fetches /me directly with the share key without exchanging the password for a token; owner changed the link password so the cached token no longer matches.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/cd4a8c2a38e3b999. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/shared-link.service.ts:58

      throw new UnauthorizedException('Invalid password');
    }

    return {
      sharedLink: mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif }),
      token: this.asToken({ id, password }),
    };
  }

  async getMine(auth: AuthDto, authTokens: string[]) {
    if (!auth.sharedLink) {
      throw new ForbiddenException();
    }

    const sharedLink = await this.findOrFail(auth.user.id, auth.sharedLink.id);
    const { id, password } = sharedLink;

    if (password && !authTokens.includes(this.asToken({ id, password }))) {
      throw new UnauthorizedException('Password required');
    }

    return mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif });
  }

  async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {
    const sharedLink = await this.findOrFail(auth.user.id, id);
    return mapSharedLink(sharedLink, { stripAssetMetadata: false });
  }

  async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {
    switch (dto.type) {
      case SharedLinkType.Album: {
        if (!dto.albumId) {
          throw new BadRequestException('Invalid albumId');
        }
        await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [dto.albumId] });
        break;

View on GitHub (pinned to e55ac299a4)