elastic/kibana · Archived

api-authz

Kibana API route authorization patterns. Use when configuring route security, working with requiredPrivileges or extendedPrivileges, using authzResult for privilege-based branching, opting out of authorization, or naming custom privileges.

First seen May 11, 2026

Installation

$ npx skills add elastic/kibana --skill api-authz

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from elastic/kibana · top by installs.

npx skills add elastic/kibana

Browse all from elastic/kibana

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 21.3K
License licenses
Default branch main
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,507 B
  • docs SUMMARY.md 256 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 3 installs

SKILL.md

API Authorization

All API routes in Kibana must have authorization checks. Authorization is not optional, even for internal routes.

Route Security Configuration

Routes declare authorization via the security option in KibanaRouteOptions:

router.get({
  path: '/api/path',
  security: {
    authz: {
      requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
      // Optional: checked and surfaced in request.authzResult, but not enforced
      // extendedPrivileges: ['<optional_privilege>'],
    },
  },
  ...
}, handler);

Privilege Naming

Privilege names follow the <operation>_<subject> convention using underscores only.

Incorrect Why Correct
read-entity-a Uses - instead of _ readentitya
delete_entity-a Mixes _ and - deleteentitya
entity_manage Subject before operation manage_entity

Privilege-Based Branching with authzResult

When a route handler branches logic based on user privileges (returns different data, enables different features), it must use request.authzResult. Do not use capabilities.resolveCapabilities() or other authorization checks for branching — authzResult is the single source of truth.

Look for: routes that conditionally expose data based on permissions, or functions that check capabilities and return booleans for branching.

Optional privileges that extend behavior (extendedPrivileges)

When a privilege gates access and another privilege only extends what the route returns or allows, declare the gate in requiredPrivileges and the optional checks in extendedPrivileges. Extended privileges are checked and surfaced in authzResult but never produce a 403.

extendedPrivileges is a flat list of privilege name strings only. Privilege sets (anyRequired / allRequired) are not supported there. When authz is enabled, requiredPrivileges is always required by the schema — optional privileges alone cannot protect a route.

Correct — required gate + optional extension:

router.get({
  path: '/api/path',
  security: {
    authz: {
      requiredPrivileges: ['read_entity'],
      extendedPrivileges: ['read_entity_details'],
    },
  },
  ...
}, (context, request, response) => {
  const includeDetails = request.authzResult?.read_entity_details === true;
  return response.ok({ body: getEntity({ includeDetails }) });
});

Wrong — abusing anyRequired then re-enforcing in the handler:

router.get({
  path: '/api/path',
  security: {
    authz: {
      // Under-declares the real requirement; OAS implies either privilege alone grants access
      requiredPrivileges: [{ anyRequired: ['read_entity', 'read_entity_details'] }],
    },
  },
}, (context, request, response) => {
  if (request.authzResult?.read_entity !== true) {
    return response.forbidden(); // easy to forget → under-protected route
  }
  const includeDetails = request.authzResult?.read_entity_details === true;
  return response.ok({ body: getEntity({ includeDetails }) });
});

Mutually exclusive privilege branches (anyRequired)

When either of several privileges grants access and the handler picks a branch, use anyRequired and branch on authzResult:

router.get({
  path: '/api/path',
  security: {
    authz: {
      requiredPrivileges: ['privilege_3', { anyRequired: ['privilege_1', 'privilege_2'] }],
    },
  },
  ...
}, (context, request, response) => {
  const authzResult = request.authzResult;
  // { "privilege_3": true, "privilege_1": true, "privilege_2": false }

  if (authzResult.privilege_1) {
    return response.ok({ body: ... });
  } else if (authzResult.privilege_2) {
    return response.ok({ body: ... });
  }

  return response.ok({ body: { data: ... } });
});

Wrong — using capabilities for authorization branching:

const canReadDecryptedParams = async (routeContext: RouteContext) => {
  const { request, server } = routeContext;
  const capabilities = await server.coreStart.capabilities.resolveCapabilities(request, {
    capabilityPath: 'my_capability.*',
  });
  return capabilities.my_capability?.canReadParams ?? false;
};

if (await canReadDecryptedParams(routeContext)) {
  return getDecryptedParams(routeContext, paramId);
} else {
  return getBasicParams(routeContext, paramId);
}

Fix: declare both privileges in the route config with anyRequired and branch on request.authzResult:

router.get({
  path: '/api/params',
  security: {
    authz: {
      requiredPrivileges: [{ anyRequired: ['read_params_decrypted', 'read_params'] }],
    },
  },
}, (context, request, response) => {
  if (request.authzResult.read_params_decrypted) {
    return getDecryptedParams(routeContext, paramId);
  } else {
    return getBasicParams(routeContext, paramId);
  }
});

Opting Out of Authorization

When a route must opt out, use the predefined AuthzOptOutReason enum or AuthzDisabled helpers from @kbn/core-security-server:

import { AuthzDisabled, AuthzOptOutReason } from '@kbn/core-security-server';

// Predefined helper
router.get({
  path: '/api/path',
  security: { authz: AuthzDisabled.delegateToSOClient },
  ...
}, handler);

// Predefined enum
router.get({
  path: '/api/path',
  security: {
    authz: { enabled: false, reason: AuthzOptOutReason.DelegateToSOClient },
  },
  ...
}, handler);

// Custom reason — only when no predefined reason applies
router.get({
  path: '/api/health',
  security: {
    authz: {
      enabled: false,
      reason: 'This route is a health check endpoint that returns no sensitive information',
    },
  },
  ...
}, handler);

Invalid opt-out reasons — flag these:

  • "Opt out from authorization" — too generic, no context
  • "This route does not need authorization" — no explanation why
  • "Authorization not required" — no context provided
  • "Authorization is delegated to SO Client" — use AuthzOptOutReason.DelegateToSOClient instead

References

  • [Kibana API Authorization Documentation](devdocs/keyconcepts/api_authorization.mdx)
  • [Kibana HTTP API Design Guidelines](devdocs/contributing/kibanahttpapidesign_guidelines.mdx)