{
  "schema_version": 2,
  "id": "operate/iris/identity-service/authentication",
  "title": "Authentication and authorization",
  "url": "https://redis.io/docs/latest/operate/iris/identity-service/authentication/",
  "summary": "Understand the Identity Service credentials and roles, let operators sign in with your identity provider, and give IdP users subject grants.",
  "tags": [
    "docs",
    "operate",
    "iris"
  ],
  "last_updated": "2026-11-03T16:45:14-08:00",
  "page_type": "content",
  "content_hash": "2a3601d190a68d460ad58dfc5ca636a89c383ed2e19693133e5b855ad82b1153",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "The Identity Service uses separate credentials for each job: operators manage agent keys through its control API, service planes call its runtime API, and agents present agent keys to service data planes. This page lists each credential and the roles on the control API. It then shows how to let operators sign in with your identity provider (IdP) and how to give IdP users access with subject grants."
    },
    {
      "id": "credentials",
      "title": "Credentials",
      "role": "content",
      "text": "| Credential | Who presents it to whom | Secret / key | What it unlocks |\n| --- | --- | --- | --- |\n| Control token | Operator → Identity Service control API (`:9200`) | `control.existingSecret` / `token`, or the generated `<release>-iris-identity-service-control-token` | Every control operation: create, list, update, rotate, and revoke agent keys, and manage subject grants. |\n| Operator access token | Operator → Identity Service control API | None. Issued by your IdP | The control operations of the operator's roles, from `controlAuth.roleBindings`. |\n| Workload token | Kubernetes workload → Identity Service control API | None. A projected service account token | Subject grant operations only, from `controlAuth.workloadIdentity`. |\n| Runtime credential | Service data plane → Identity Service runtime API | The shared Secret in a `runtime.serviceCredentials[]` entry | Checking agent keys, and reading subject grants when allowed, for the entry's services. |\n| Internal token | Identity Service → service control plane | The shared Secret in `productValidation.products.<product>.credential` | Grant validation: confirming that a resource exists before a key is stored. |\n| Agent key | Agent → service data plane | None. Returned once when minted or rotated | The resources and actions its grants name. |\n\nEach credential needs its own value. The Identity Service doesn't start when two credentials have the same value, and the chart refuses to render when two reference the same Secret key.\n\nThe control token is a shared secret, and the audit log can't name a person for it. Keep it for setup and recovery, and let operators sign in with your IdP for daily work."
    },
    {
      "id": "agent-keys-and-grants",
      "title": "Agent keys and grants",
      "role": "content",
      "text": "An agent key looks like `iris_<key-id>.<secret>`. The Identity Service returns the full key only in the response to a mint or a rotation, and stores only a SHA-256 hash of it. If you lose a key, rotate it or mint a new one.\n\nEach key carries one or more grants:\n\n| Grant field | Meaning | Example |\n| --- | --- | --- |\n| `tenant` | The installation's tenant. If you omit it, the Identity Service adds `runtime.apiKeyTenant.singleTenantID`. | `default` |\n| `product` | The Iris service. | `memory` |\n| `resourceType` | The type of resource in that service. | `mem-store` |\n| `resourceId` | One resource. Wildcards aren't accepted. | `<store-id>` |\n| `actions` | What the key can do with the resource. | `[\"read\", \"write\"]` |\n\n| Service | `product` | `resourceType` | Actions |\n| --- | --- | --- | --- |\n| Agent Memory | `memory` | `mem-store` | `read`; `write` (includes `read`); `full` (includes `write`) |\n| Agent Playbooks | `playbook` | `playbook` | `read`; `write`, `publish`, and `propose` (each includes `read`); `full` (includes all) |\n| Context Gateway | `context-gateway` | `gateway` | `use`; `full` (includes `use`) |\n| Intent Router | `intent` | `intent-router` | `read`; `write` (includes `read`); `full` (includes `write`) |\n| Lens | `lens` | `lens-instance` | `ingest` and `read`, independent of each other; `full` (includes both) |\n\nAll the grants of one key use the same tenant. Each service's authentication page explains what its actions allow. To mint and manage keys, see [Manage agent keys](https://redis.io/docs/latest/operate/iris/identity-service/operations#manage-agent-keys)."
    },
    {
      "id": "roles-on-the-control-api",
      "title": "Roles on the control API",
      "role": "content",
      "text": "| Caller | How it gets the role | What it can do |\n| --- | --- | --- |\n| Control token | It holds the token | Every operation. |\n| `suite_admin` | A role binding with `scope: global` | Every operation for every service, in its own tenant only. |\n| `product_admin` | A role binding with `scope: product` and a `product` | Every operation for that service, in its own tenant only. |\n| `subject_grant_admin` | A workload identity subject | Subject grant operations only, in its own tenant. |\n| `deployment_admin` | A role binding with `scope: deployment` and an `issuer` | Support work across tenants: list, read, and revoke keys, and list and delete subject grants. It can't create, update, or rotate keys, or add grants. An installation with one tenant doesn't need it. |\n| Any other caller | None | Nothing. The request gets `403`. |\n\nThe service control-plane roles, such as `operator` and `viewer`, give no access to the Identity Service control API. An operator who must mint keys needs `suite_admin` or `product_admin` here, in addition to their role on the service's control plane.\n\nIf one of an operator's roles is `deployment_admin`, that operator can't create, update, or rotate keys, also in their own tenant and also when they have another role. Give `deployment_admin` only to people who don't need those operations."
    },
    {
      "id": "operator-sign-in-with-your-identity-provider",
      "title": "Operator sign-in with your identity provider",
      "role": "content",
      "text": "Operators can call the control API with an access token from your IdP, using OpenID Connect (OIDC). The audit log then names each operator, and you can grant access by IdP group. The control token keeps working."
    },
    {
      "id": "1-register-iris-in-your-idp",
      "title": "1. Register Iris in your IdP",
      "role": "content",
      "text": "1. Register one API for the whole Iris suite with one audience, for example `api://redis-iris`. On Microsoft Entra ID v2, the audience is the API's application (client) ID. Use the same audience that the service control planes accept.\n1. Create groups or app roles for key administrators, for example `iris-suite-admins` and `memory-admins`, and assign operators to them.\n1. Make sure the group or role values reach the access token. On Entra ID, prefer app roles: raw `groups` claims arrive as IDs and are dropped for users in many groups. On Okta, add a groups claim with a filter.\n1. Restrict the application to the assigned groups. On Entra ID, set **User assignment required** to **Yes**."
    },
    {
      "id": "2-configure-controlauth",
      "title": "2. Configure `controlAuth`",
      "role": "content",
      "text": "Add these values to `values-ids.yaml`:\n\n[code example]\n\n- The fields under `oidc` use snake_case, because the chart copies them into the service configuration as written.\n- `issuer` must equal the token's `iss` claim exactly, including its scheme. The Identity Service never fetches it. On Active Directory Federation Services (AD FS), use the discovery document's `access_token_issuer`.\n- `jwks_uri`, the JSON Web Key Set (JWKS) URI, must use `https://`. The pod must reach it and trust its CA. See [TLS, FIPS, and network](https://redis.io/docs/latest/operate/iris/identity-service/configuration#tls-fips-and-network).\n- Role bindings match the values of the `roles` and `groups` claims by default. Set `role_claims` under `oidc` to read other claims. A `claim_value` matches without regard to letter case.\n- With `runtime.apiKeyTenant.singleTenantID` set, the chart adds the tenant for you. Rendering fails if you also set `tenant_id_claim` or a different `static_tenant_id`.\n- The control API refuses `allow_unmapped_roles`. Use `roleBindings` for every role."
    },
    {
      "id": "3-apply-and-check",
      "title": "3. Apply and check",
      "role": "content",
      "text": "1. Run `helm upgrade ids redis-ai/iris-identity-service --version 1.0.0 -n <namespace-name> -f values-ids.yaml`.\n1. Get an access token for an operator from your IdP.\n1. Call the control API with it:\n\n   [code example]\n\n| Status | Meaning |\n| --- | --- |\n| `200` | The token is valid and a role binding matches. |\n| `401` | The issuer, audience, or signature is wrong, or the token expired. Compare the token's `iss` and `aud` claims with `controlAuth.oidc`. |\n| `403` | The token is valid, but no role binding matches its groups, or the role doesn't cover this operation or service. |\n| `503` | The Identity Service can't reach the JWKS URI. The control token still works. |\n\nThe `iris` command-line interface (CLI) accepts an operator access token in `IRIS_IDENTITY_TOKEN` in place of the control token."
    },
    {
      "id": "remove-a-role-binding",
      "title": "Remove a role binding",
      "role": "content",
      "text": "To revoke a binding that gives too much access, add a [policy overlay](https://redis.io/docs/latest/operate/iris/identity-service/configuration#configuration-overlays) Secret with the same binding and `role: none`, then change `secrets.checksum` and run `helm upgrade`."
    },
    {
      "id": "subject-grants",
      "title": "Subject grants",
      "role": "content",
      "text": "A subject grant gives an IdP user or workload access to a service resource without an agent key. The service's data plane checks the user's token itself, then asks the Identity Service for the grants of that subject. The Identity Service never sees the user's token.\n\nContext Gateway reads subject grants for users who sign in with your IdP. Agent Memory and Agent Playbooks read them only for service subjects. Lens and Intent Router don't use them.\n\nTo give IdP users access to a Context Gateway:\n\n1. Add `subject-grant-lookup` to the data plane's runtime credential:\n\n   [code example]\n\n1. Allow the grant shape:\n\n   [code example]\n\n1. Run `helm upgrade` for the Identity Service release.\n1. Give the grant to the user. The `subject` is the value of the user's `sub` claim, or of the claim that the data plane reads as the user ID:\n\n   [code example]\n\n   The control token must send `issuer`. An operator who signs in with the IdP can omit it, and the Identity Service uses the issuer of the operator's token.\n\n1. To remove the grant, send the same request with `\"<gateway-id>\": null`. The data plane can keep using its cached grants for up to `runtime.cache.hardTtlSeconds`.\n\nThe issuer must equal the user token's `iss` claim exactly. Don't use an email address as the subject, because a user can change it.\n\nThese resource types accept subject grants:\n\n| `resourceType` | Allowed actions |\n| --- | --- |\n| `mem-store` | `read`, `write`, `full`, `read_all_owners` |\n| `playbook` | `read`, `propose` |\n| `gateway` | `use`, `full` |\n\nIf you remove a shape from `subjectGrants.allowed`, the Identity Service stops returning grants of that shape after its next restart. It doesn't delete them."
    },
    {
      "id": "workload-identity",
      "title": "Workload identity",
      "role": "content",
      "text": "A Kubernetes workload can manage subject grants with its own projected service account token instead of the control token:\n\n[code example]\n\nTake the issuer and the JWKS URI from the cluster's `/.well-known/openid-configuration`. A subject can't get `suite_admin`. A subject without a `tenant` gets `runtime.apiKeyTenant.singleTenantID`.\n\nAgent Playbooks extraction from Agent Memory needs no workload identity on the Identity Service."
    },
    {
      "id": "recover-from-a-lockout",
      "title": "Recover from a lockout",
      "role": "content",
      "text": "- **Your IdP is down, or the role bindings are wrong.** Use the control token. It always works.\n- **You lost the control token, and the chart generated it.** Delete its Secret, if it still exists, and run `helm upgrade`. The chart generates a new token. If you supply the token with `control.existingSecret`, put a new value in your Secret. The Identity Service reads the token file on each request, so the new value works after Kubernetes updates the mounted file, in about a minute.\n- **You lost the metadata Redis.** Restore it from your backup. See [Back up and restore](https://redis.io/docs/latest/operate/iris/identity-service/operations#back-up-and-restore)."
    }
  ],
  "examples": [
    {
      "id": "2-configure-controlauth-ex0",
      "language": "yaml",
      "code": "controlAuth:\n  oidc:\n    enabled: true\n    issuer: https://<idp-host>/<issuer-path>\n    jwks_uri: https://<idp-host>/<jwks-path>\n    audience: [api://redis-iris]\n  roleBindings:\n    - {claim_value: iris-suite-admins, scope: global, role: suite_admin}\n    - {claim_value: memory-admins, scope: product, product: memory, role: product_admin}",
      "section_id": "2-configure-controlauth"
    },
    {
      "id": "3-apply-and-check-ex0",
      "language": "bash",
      "code": "curl -sS -o /dev/null -w '%{http_code}\\n' \\\n     -H \"Authorization: Bearer <operator-access-token>\" \\\n     \"$IRIS_IDENTITY_URL/v1/api-keys\"",
      "section_id": "3-apply-and-check"
    },
    {
      "id": "subject-grants-ex0",
      "language": "yaml",
      "code": "runtime:\n     serviceCredentials:\n       - name: context-gateway-dp\n         subject: context-gateway-dataplane\n         allowedOperations: [api-key-introspect, subject-grant-lookup]\n         allowedProducts: [context-gateway]\n         existingSecret: gateway-runtime\n         secretKey: token",
      "section_id": "subject-grants"
    },
    {
      "id": "subject-grants-ex1",
      "language": "yaml",
      "code": "subjectGrants:\n     allowed:\n       - {product: context-gateway, resourceType: gateway, actions: [use]}",
      "section_id": "subject-grants"
    },
    {
      "id": "subject-grants-ex2",
      "language": "bash",
      "code": "curl -sS -X POST \"$IRIS_IDENTITY_URL/v1/subject-grants/apply\" \\\n     -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\" \\\n     -H 'Content-Type: application/json' \\\n     -d '{\n       \"issuer\": \"https://<idp-host>/<issuer-path>\",\n       \"subject\": \"<user-id>\",\n       \"product\": \"context-gateway\",\n       \"grants\": {\"gateway\": {\"<gateway-id>\": {\"actions\": [\"use\"]}}}\n     }'",
      "section_id": "subject-grants"
    },
    {
      "id": "workload-identity-ex0",
      "language": "yaml",
      "code": "controlAuth:\n  workloadIdentity:\n    enabled: true\n    issuer: <cluster-service-account-issuer>\n    jwks_uri: <cluster-jwks-uri>\n    audience: [<audience>]\n    subjects:\n      - subject: system:serviceaccount:<namespace-name>:<service-account-name>\n        roles: [subject_grant_admin]",
      "section_id": "workload-identity"
    }
  ]
}
