Skip to main content
Version: 0.12.0

API keys (personal access tokens)

Introduced 0.12.0

An API key is a native, revocable personal access token (PAT) issued and verified entirely by Lucenia. It is the practical way for a person or an automated agent to authenticate any request to the cluster without embedding a password or minting a fresh token for every call. A key is a pair — an id and a high-entropy secret — bound to an identity, carrying a bounded set of roles, and expiring on a schedule. The secret is never stored in plaintext (only a bcrypt hash is kept) and a key can be revoked at any time. Any part of the product that authorizes requests works with a key-authenticated caller exactly as it does with any other user; the memory verbs are one such consumer (see Using API keys with memory tenancy).

How API keys compare with the other token-based backends:

  • HTTP basic authentication verifies a username and password against the internal user database on every request. An API key stores no recoverable secret — only a bcrypt hash — and carries its own expiration and revocation.
  • JSON Web Tokens are self-contained tokens minted and signed by an external identity provider; Lucenia only verifies the signature and cannot revoke an individual token. An API key needs no external IdP — Lucenia issues it — and it is individually revocable at any time.
tip

API keys authenticate to the whole cluster, not to any single feature. They are the token most teams hand to an agent (for example, a coding assistant calling Lucenia over MCP) so it acts as a specific user with a narrow, revocable set of roles.


Table of contents


Enable API keys

Two independent switches govern API keys, and both must be on for keys to authenticate requests:

  1. The management feature — creating, listing, and revoking keys — is controlled by the node setting plugins.security.apikey.enabled, which defaults to true. When it is false, every API-key REST call returns 400 "The API key feature is disabled on this node."
  2. The authenticator — actually logging in with a key — is off until you enable the apikey_auth_domain in the security config.yml. The feature being enabled lets you manage keys; the auth domain being enabled lets a presented key authenticate a request.

The reference config.yml ships the domain disabled:

apikey_auth_domain:
description: "Authenticate via native, revocable API keys (personal access tokens)"
http_enabled: false # set to true to accept API-key logins
transport_enabled: false
order: 1
http_authenticator:
type: apikey
challenge: false
authentication_backend:
type: noop

Set http_enabled: true and load the security configuration. Because the token already carries everything needed to identify the caller, the domain is non-challenging (challenge: false) and uses a noop authentication backend.

note

Ordering requirement. A non-challenging authenticator only runs if it is evaluated before a challenging one. Keep apikey_auth_domain at a lower order than the challenging basic_internal_auth_domain (the reference configuration ships the API-key domain at order: 1 and the basic domain at order: 4). If the API-key domain is placed after basic, basic issues its 401 first and the API-key authenticator never gets a chance to run.

Settings

All settings are node-scoped.

SettingDefaultPurpose
plugins.security.apikey.enabledtrueMaster switch for the create/list/get/revoke management API and the authenticator.
plugins.security.apikey.default_expiration30dExpiration applied when a create request omits one.
plugins.security.apikey.max_expiration365dHard cap; a requested expiration is clamped to this.
plugins.security.apikey.cache_refresh_interval10sHow often each node reloads the key set from the index (bounds cross-node revocation lag).
plugins.security.apikey.index_name.opendistro_security_api_keysProtected system index backing the stored keys.

Create (mint) a key

PUT /_plugins/_security/api/apikey. The body needs a name; everything else is optional.

PUT /_plugins/_security/api/apikey
{
"name": "ci-deploy-bot",
"security_roles": ["readall"],
"backend_roles": ["automation"],
"expiration": "30d"
}
FieldTypeNotes
namestringRequired. A label for the key.
security_rolesarrayInternal security roles to grant the key. Also accepted under the alias roles.
backend_rolesarrayBackend roles the key carries (subject to role mapping).
expirationstringA duration such as "30d". Defaults to default_expiration, capped at max_expiration.

The response returns the token once — the secret is shown here and never again:

{
"id": "k-8f2a...",
"name": "ci-deploy-bot",
"api_key": "azLThjJhLi4uLi4uOnNlY3JldA==",
"expiration_at": 1754092800000
}

api_key is the ready-to-use token: a base64 encoding of id:secret. Capture it now — the secret is stored only as a bcrypt hash and cannot be retrieved later. If you lose it, revoke the key and mint a new one.

Use a key

Send the token in the Authorization header with the ApiKey scheme:

Authorization: ApiKey azLThjJhLi4uLi4uOnNlY3JldA==

The value is the api_key string from the create response — i.e. base64(id:secret). The scheme keyword is case-insensitive. Every request so authenticated runs as the key's identity, with the key's roles, and is subject to the same authorization checks as any other user. As with basic auth and JWTs, always transmit keys over HTTPS.

POST /my-index/_search
Authorization: ApiKey azLThjJhLi4uLi4uOnNlY3JldA==
Content-Type: application/json

{ "query": { "match_all": {} } }

List and get keys

GET /_plugins/_security/api/apikey returns every key's metadata (never the secret or its hash):

{
"api_keys": [
{
"id": "k-8f2a...",
"name": "ci-deploy-bot",
"backend_roles": ["automation"],
"security_roles": ["readall"],
"creator": "release-manager",
"created_at": 1751500800000,
"expiration_at": 1754092800000,
"invalidated": false
}
]
}

GET /_plugins/_security/api/apikey/{id} returns a single key's metadata (404 if unknown). The stored secret hash is never included in either response.

Revoke a key

DELETE /_plugins/_security/api/apikey/{id} revokes by id:

{ "id": "k-8f2a...", "invalidated": true }

Revocation is a soft invalidate — the key is marked invalidated (its document is retained, not deleted) and it stops authenticating. A 404 is returned for an unknown id.

warning

Cross-node revocation lag. Each node authenticates keys from an in-memory cache refreshed on plugins.security.apikey.cache_refresh_interval (default 10s). Revocation is immediate on the node that handles the DELETE, but other nodes converge within one refresh interval — so a revoked key may remain valid on other nodes for up to that interval. Lower cache_refresh_interval to tighten the window (at the cost of more frequent reloads).

Limit-by-owner

A key can never grant more authority than its creator holds:

  • When an authenticated user creates a key, every requested security_roles and backend_roles entry must be a role the creator already holds. Requesting a role the creator lacks returns 403.
  • If the create request supplies no roles, the key inherits the creator's own security and backend roles.

So a key is always a subset of its creator's authority and can never escalate privileges. Grant a key exactly the roles it needs and no more. (A request authenticated by a trusted cluster admin certificate is not subject to this restriction, since it is already gated by the route permission below.)

Security properties

  • High-entropy secret. The secret is 32 bytes (256 bits) drawn from a SecureRandom.
  • Hashed at rest. Only a bcrypt hash of the secret is persisted (produced by the same hasher used for internal-user passwords); the plaintext secret is never stored and is returned exactly once at creation.
  • Constant-time verification. A presented secret is checked against the stored hash in constant time — a bcrypt verification runs even for an unknown key id — so verification timing does not leak whether an id exists.
  • Protected index. Keys live in a protected system index (default .opendistro_security_api_keys) that only the security module writes to (internally, with superadmin privileges). It cannot be read or written directly through the normal REST/index APIs, so key material cannot be exfiltrated or forged by writing to the index.

Permissions

Each management route is gated by its own cluster permission, so you can delegate key administration narrowly:

OperationEndpointPermission
CreatePUT /_plugins/_security/api/apikeysecurity:apikey/create
ListGET /_plugins/_security/api/apikeysecurity:apikey/list
Get oneGET /_plugins/_security/api/apikey/{id}security:apikey/get
RevokeDELETE /_plugins/_security/api/apikey/{id}security:apikey/invalidate