immich-app/immich · warning

Denied access to admin only route

Error message

Denied access to admin only route: ${uri}

What it means

During request authentication (validate + authorize), if the resolved user is not an admin but the route is flagged adminRoute, access is denied: this warning is logged with the URI and a ForbiddenException('Forbidden') is thrown. This is the server enforcing role-based access on admin-only endpoints.

Solutions

  1. Use an administrator account or an admin's API key for admin-only endpoints.
  2. If the user should be admin, promote them in the admin Users page.
  3. For automations, find a non-admin endpoint or scope the integration appropriately.
  4. Confirm the request isn't accidentally routed to an admin endpoint due to a wrong URL.
Defensive patterns

Strategy: validation

Validate before calling

// client-side guard before calling an admin API
const user = await api.getMyUser();
if (!user.isAdmin) throw new Error('Admin privileges required for this endpoint');

Type guard

const isAdminUser = (u: { isAdmin: boolean }): u is { isAdmin: true } => u.isAdmin === true;

Try / catch

try {
  await api.adminEndpoint();
} catch (e) {
  if (e instanceof ForbiddenException) {
    showToast('This action requires an administrator account');
  } else throw e;
}

Prevention

When it happens

Trigger: Any API call to a route whose metadata marks adminRoute=true while the authenticated user's isAdmin is false.

Common situations: A non-admin user hitting admin endpoints (user management, server settings, jobs) directly via API; custom scripts/integrations using an admin-only endpoint with a normal user's API key.

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/7506195d9d9f56db. Report an issue: GitHub.

Appendix: source

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

  async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
    const admin = await this.createUser({
      isAdmin: true,
      email: dto.email,
      name: dto.name,
      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;
  }

View on GitHub (pinned to f48d4b3321)