Enterprise authentication for memory
Memory's tenant isolation is only as strong as the identity behind it. This page shows how to make the organization boundary work for each standard authentication backend the security plugin supports — JWT/OIDC, LDAP/AD, SAML, internal users, and client certificates — so that every authenticated caller is attributed to the right organization automatically.
The good news for most deployments: if you authenticate with OIDC (or any JWT) and your tokens carry an org claim, it works out of the box — the default org source is the JWT claim org. Every other backend is a one-setting change.
The mechanics of the boundary — how organization and user are pinned from the verified identity and why a mismatch is a 403 — are in Memory tenancy and isolation. This page is the identity-provider cookbook.
Table of contents
How the organization is sourced
After the security plugin verifies a caller, the memory layer derives the caller's organization from that verified identity in a fixed order:
- Named identity attribute — the value of the attribute named by
plugins.memory.tenancy.org_attribute(defaultattr.jwt.org). - Prefixed backend role — if the attribute is absent and
plugins.memory.tenancy.org_backend_role_prefixis set, the first backend role starting with that prefix yields the organization (backend roleorg:acme→ organizationacme). - Fallback — if neither resolves:
403whenplugins.memory.tenancy.require_orgistrue, otherwise the organizationdefault.
The three tenancy settings, all node-scoped and dynamic:
| Setting | Default | Meaning |
|---|---|---|
plugins.memory.tenancy.org_attribute | attr.jwt.org | The identity attribute the organization is read from. |
plugins.memory.tenancy.org_backend_role_prefix | (empty — disabled) | A backend-role prefix that selects the organization from a group/role. |
plugins.memory.tenancy.require_org | false | Fail-closed: refuse identities that resolve to no organization. |
The two mechanisms — attribute and backend-role prefix — are the whole toolkit. Which one you use for a given backend depends on whether that backend delivers the organization as an attribute (JWT claims, LDAP attributes, internal-user attributes) or as a group/role (LDAP/AD groups, SAML groups, mapped backend roles). The recipes below pick the natural one for each.
The user dimension is never configured — it is always the authenticated user's name, established by whichever backend authenticated the request. You only ever configure how the organization is sourced.
JWT / OIDC (Keycloak, Auth0, Okta, Azure AD) — works out of the box
Every claim in a verified JWT is surfaced to the identity as attr.jwt.<claim>. The default org_attribute is attr.jwt.org, so if your identity provider mints tokens with an org claim, memory is already sourcing the organization correctly — there is nothing to configure.
Add an org claim in your IdP:
- Keycloak — add a user attribute
organd a protocol mapper that emits it as a token claimorg. - Auth0 — add
orgvia a Login Action / custom claim (namespaced claims also work; pointorg_attributeat the namespaced name if you use one). - Okta — add an
orgclaim to the authorization server's access-token claims. - Azure AD (Entra ID) — emit
orgas an optional/custom claim (or a group claim; see below).
If your claim is named something other than org (say tenant), point the setting at it:
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_attribute": "attr.jwt.tenant"
}
}
That is the only change. OIDC is the standard JWT flow with discovery via an openid_connect_url in the security config.yml; from memory's perspective it is identical — the ID/access token's claims arrive as attr.jwt.*.
If your IdP delivers the tenant as a group rather than a scalar claim (common with Azure AD group claims), use the backend-role-prefix approach from the SAML/AD sections below instead: map the group to a backend role like org:acme and set org_backend_role_prefix.
LDAP / Active Directory
LDAP delivers two kinds of signal, and either can carry the organization:
Option A — an LDAP attribute. Allow-listed directory attributes are surfaced as attr.ldap.<attribute>. If your directory records the organization on the user entry (for example the o — organization — attribute), point the setting at it:
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_attribute": "attr.ldap.o"
}
}
(Ensure that attribute is in the security plugin's LDAP user-attribute allow-list so it is surfaced.)
Option B — an AD group as a backend role. LDAP/AD group memberships resolve to backend roles. Give each tenant a group whose name encodes the organization — org:acme — and switch memory to the backend-role source:
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_backend_role_prefix": "org:"
}
}
A user in the AD group org:acme is now placed in organization acme. Option B is usually the better fit for AD shops that already manage tenancy through security groups.
SAML
SAML identity providers deliver group/role memberships that the security plugin resolves to backend roles. Use the backend-role-prefix mechanism: create a SAML group (or role attribute value) per tenant named org:<tenant>, and set:
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_backend_role_prefix": "org:"
}
}
An assertion carrying the group org:acme places the user in organization acme. If your SAML setup instead surfaces the tenant as a named user attribute, you can point org_attribute at that attribute name — but the group→backend-role path is the common, reliable one for SAML.
The exact attribute/group names depend on how your IdP is mapped in the security config.yml (the roles_key / attribute mapping for the SAML domain). The memory setting is provider-agnostic: it only needs the resulting backend role to start with your chosen prefix.
Internal users
For clusters using the security plugin's internal user database, you have two clean options:
Option A — a backend role. Give the internal user a backend role in internal_users.yml that names the organization, then use the prefix source:
# internal_users.yml
analyst-7:
hash: "<bcrypt>"
backend_roles:
- "org:acme"
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_backend_role_prefix": "org:"
}
}
Option B — a user attribute. Internal-user attributes surface as attr.internal.<name>. Set an org attribute on the user and point the setting there:
# internal_users.yml
analyst-7:
hash: "<bcrypt>"
attributes:
org: "acme"
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_attribute": "attr.internal.org"
}
}
Client certificates (PKI / mTLS)
Certificate authentication establishes the user from the certificate subject (for example the CN) but carries no group memberships of its own. Attribute the organization by mapping the certificate identity to a backend role in the security plugin's roles_mapping, then use the prefix source:
# roles_mapping.yml — grant the org backend role to the cert identity
org_acme_members:
backend_roles:
- "org:acme"
users:
- "CN=analyst-7,OU=acme,O=Example"
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.org_backend_role_prefix": "org:"
}
}
The mapped backend role org:acme is what memory reads. (If you provision one certificate per tenant with a tenant-specific OU/O, mapping is straightforward.)
Fail-closed for compliance: require_org
By default, an identity that resolves to no organization lands in the shared default organization. For a compliance posture, make that a hard denial instead:
PUT _cluster/settings
{
"persistent": {
"plugins.memory.tenancy.require_org": true
}
}
With require_org: true, any authenticated caller whose token/directory record does not yield an organization is rejected with 403 at the memory verb — no writes, no recalls, no silent pooling into default. Turn this on once every legitimate identity in your directory carries an organization, so a mis-provisioned user fails loudly rather than leaking into a shared bucket.
Enable require_org after you have confirmed your identities resolve correctly (check a real call, or that the intended organization's index receives writes). Turning it on before every identity carries an organization will 403 those callers.
Quick reference
| Backend | Organization carried as | Setting to use |
|---|---|---|
| JWT / OIDC | org claim | (none — default attr.jwt.org), or org_attribute: attr.jwt.<claim> |
| LDAP / AD (attribute) | directory attribute (e.g. o) | org_attribute: attr.ldap.<attr> |
| LDAP / AD (group) | AD group org:<tenant> | org_backend_role_prefix: "org:" |
| SAML | SAML group org:<tenant> | org_backend_role_prefix: "org:" |
| Internal users | backend role org:<tenant> | org_backend_role_prefix: "org:" |
| Internal users | user attribute org | org_attribute: attr.internal.org |
| Client cert (PKI) | mapped backend role org:<tenant> | org_backend_role_prefix: "org:" |
| Any, compliance | — | require_org: true (fail-closed) |
Related pages
- Memory tenancy and isolation — how the resolved organization is enforced.
- Enable and configure memory — provision organizations and enable the MCP server.
- API keys for memory — personal access tokens that carry an identity (and its backend roles) into the verbs.