immich-app/immich · warning

Denied access to non-shared route

Error message

Denied access to non-shared route: ${uri}

What it means

When a request is authenticated via a shared link, access is restricted to routes explicitly marked sharedLinkRoute. If a shared-link session attempts any other route, this warning is logged with the URI and a ForbiddenException is thrown. It prevents shared-link visitors from using general API surface.

Solutions

  1. Ensure the shared-link frontend only calls shared-link-permitted endpoints.
  2. Use a full authenticated user session/API key instead of a shared link for those endpoints.
  3. Check custom integrations: shared links cannot act as general API credentials.
  4. If a legitimate shared route is blocked, verify the route metadata is flagged sharedLinkRoute.
Defensive patterns

Strategy: validation

Validate before calling

// use only shared-link-permitted endpoints when authenticated via a shared link
if (authMethod === 'shared-link') {
  assert(allowedSharedRoutes.has(endpoint), `${endpoint} is not accessible via shared link`);
}

Try / catch

try {
  await api.call(endpoint);
} catch (e) {
  if (e instanceof ForbiddenException && usingSharedLink) {
    showToast('Shared links can only access shared content');
  } else throw e;
}

Prevention

When it happens

Trigger: A request authenticated with a shared link (authDto.sharedLink set) targets a route whose metadata does not include sharedLinkRoute=true.

Common situations: Shared-link page code or a manual client calling non-shared endpoints (e.g. /api/users, albums API) with shared-link credentials; stale clients probing routes with link tokens.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/auth.service.ts:226

      password: dto.password,
      storageLabel: 'admin',
    });

    return mapUserAdmin(admin);
  }

  async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
    const authDto = await this.validate({ headers, queryParams });
    const { adminRoute, sharedLinkRoute, uri } = metadata;
    const requestedPermission = metadata.permission ?? Permission.All;

    if (!authDto.user.isAdmin && adminRoute) {
      this.logger.warn(`Denied access to admin only route: ${uri}`);
      throw new ForbiddenException('Forbidden');
    }

    if (authDto.sharedLink && !sharedLinkRoute) {
      this.logger.warn(`Denied access to non-shared route: ${uri}`);
      throw new ForbiddenException('Forbidden');
    }

    if (
      authDto.apiKey &&
      requestedPermission !== false &&
      !isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })
    ) {
      throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);
    }

    return authDto;
  }

  private async validate({ headers, queryParams }: Omit<ValidateRequest, 'metadata'>): Promise<AuthDto> {
    const shareKey = (headers[ImmichHeader.SharedLinkKey] || queryParams[ImmichQuery.SharedLinkKey]) as string;
    const shareSlug = (headers[ImmichHeader.SharedLinkSlug] || queryParams[ImmichQuery.SharedLinkSlug]) as string;
    const session = (headers[ImmichHeader.UserToken] ||

View on GitHub (pinned to f48d4b3321)