immich-app/immich · error · ForbiddenException

Forbidden

Error message

Forbidden

What it means

authenticate enforces route metadata after validating the request. If the resolved authDto.user is not an admin but the route is flagged adminRoute, access is denied with a 403 Forbidden (and a warning is logged with the denied URI). This is the admin-only route guard for the API.

Solutions

  1. Perform the action with an administrator account or an API key created by an admin.
  2. If the user should be admin, promote them via an existing admin (or server CLI) and retry.
  3. Change the client to use the equivalent non-admin endpoint if one exists.

Example fix

// before
const apiKey = createUserApiKey(nonAdminUser, 'key'); // 403 on admin routes
// after
const apiKey = await adminApi.usersApi.createApiKey(adminUserId, 'admin-key');
Defensive patterns

Strategy: try-catch

Validate before calling

const me = await api.usersApi.getMyUser();
if (!me.isAdmin) {
  throw new Error('Admin privileges required for this endpoint');
}

Type guard

function isAdminUser(u: { isAdmin: boolean }): u is { isAdmin: true } {
  return u.isAdmin === true;
}

Try / catch

try {
  await api.usersApi.getAllUsers(); // admin route
} catch (e) {
  if (e.status === 403) {
    // surface 'admin account required' to the caller
  }
  throw e;
}

Prevention

When it happens

Trigger: A non-admin authenticated user (session, API key, or shared link holder) calls an admin-only endpoint such as user management or server-config endpoints.

Common situations: Regular users hitting admin endpoints from scripts/UIs that assume elevated rights; an API key minted by a non-admin account; role downgraded to non-admin while an old client still calls admin routes.

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

Appendix: source

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

    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)