{
  "schema_version": 2,
  "id": "operate/featureform/configure-auth",
  "title": "Configure authentication and RBAC",
  "url": "https://redis.io/docs/latest/operate/featureform/configure-auth/",
  "summary": "Configure deployment-wide authentication and RBAC for Redis Feature Form.",
  "aliases": [
    "/operate/featureform/auth/"
  ],
  "tags": [
    "docs",
    "operate",
    "featureform"
  ],
  "last_updated": "2026-08-26T14:40:32-07:00",
  "page_type": "content",
  "content_hash": "c8db0add3c84f00427620abfaf63e407f8922d8ec9748d3a35554bcc02883e0f",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Redis Feature Form authenticates users and services through an external OIDC identity provider, then enforces built-in role-based access control (RBAC) on their actions. Authentication is a deployment-wide concern configured at install time; authorization is per-workspace (with a small number of deployment-scoped exceptions) and managed at runtime through role bindings.\n\nOpenID Connect (OIDC) is a standard identity layer that lets Feature Form delegate authentication to a separate identity provider (IdP). You operate an IdP yourself (such as Keycloak) or use a managed service (such as Okta); Feature Form reads the resulting JSON Web Tokens (JWTs) to enforce its own role checks.\n\nA [workspace](https://redis.io/docs/latest/develop/ai/featureform/concepts#workspaces) isolates resources; RBAC bindings control who can act on it.\n\nThe sequence:\n\n1. [Configure OIDC at deploy time](#configure-oidc-at-deploy-time)\n2. [Register a CLI client](#register-a-cli-client)\n3. [Sign in with the CLI](#sign-in-with-the-cli)\n4. [Pick built-in roles for users and groups](#built-in-roles)\n5. [Provision the first global admin](#provision-the-first-global-admin)\n6. [Set up service accounts for non-human identities](#service-accounts-and-machine-credentials)\n7. [Read the audit log](#audit)"
    },
    {
      "id": "authentication",
      "title": "Authentication",
      "role": "security",
      "text": ""
    },
    {
      "id": "configure-oidc-at-deploy-time",
      "title": "Configure OIDC at deploy time",
      "role": "configuration",
      "text": "Set Feature Form's OIDC parameters in the Helm chart's `auth` block. `auth.oidcClientID` is the audience that Feature Form requires in access tokens. `auth.oidcCLIClientID` is the separate client that users sign in through.\n\n[code example]\n\nConfigure the IdP to include the `featureform-api` audience in access tokens issued to `featureform-cli`. Otherwise, login can succeed at the IdP while Feature Form rejects the token.\n\nUse a unique, stable `auth.deploymentID` for each environment. See [Deploy](https://redis.io/docs/latest/operate/featureform/deploy) for naming and lifecycle guidance.\n\nFor deployments where internal services reach the IdP at a different URL than external clients, use `oidcDiscoveryURL`, `oidcPublicIssuerURL`, and `oidcPublicDiscoveryURL` to split the discovery and issuer endpoints. The `oidcSkipIssuerCheck: true` flag disables issuer-claim validation. Use it only during local development.\n\nFeature Form reads role information from JWT claims on each request. It checks the following claims, in order, for matches against built-in role IDs:\n\n- `featureform_roles` (string or array)\n- `roles` (string or array)\n- `role` (string)\n- `realm_access.roles` (array; Keycloak convention)\n\nIf any of those claims contain `global_admin`, Feature Form treats the user as a global admin for that token's lifetime without a database binding. This is the typical way operators bootstrap the first admin — see [Provision the first global admin](#provision-the-first-global-admin)."
    },
    {
      "id": "register-a-cli-client",
      "title": "Register a CLI client",
      "role": "content",
      "text": "Register a dedicated CLI client with the IdP. The client settings must match the login methods advertised through `auth.oidcCLILoginMethods`.\n\n| Login method | IdP client requirements | Helm settings |\n| --- | --- | --- |\n| `device_code` | Use a public or native client with client authentication disabled. Enable the OAuth 2.0 Device Authorization Grant. No client secret or redirect URI is used. | Set `auth.oidcCLIClientID` and `auth.oidcCLILoginMethods: \"device_code\"`. |\n| `authorization_code_pkce` | Enable the Authorization Code Grant and Proof Key for Code Exchange (PKCE) with the SHA-256 (`S256`) challenge method. Register the exact redirect URI used by the CLI. A public client is recommended. | Set `auth.oidcCLIClientID`, `auth.oidcCLILoginMethods: \"authorization_code_pkce\"`, and `auth.oidcCLIRedirectURI`. |\n\nThe IdP must allow every scope in `auth.oidcCLIScopes`. The default scopes are `openid profile offline_access`. The `offline_access` scope lets the CLI request a refresh token. Remove it when the IdP doesn't support offline access; users must sign in again after their access token expires.\n\nIf the IdP requires a confidential client for PKCE, provide its secret only at login through `FEATUREFORM_LOGIN_CLIENT_SECRET` or `--login-client-secret`. Don't put the CLI client secret in Helm values. The CLI doesn't persist the secret or the refresh token from that login.\n\nIn the Helm values file for the Feature Form release, use these settings to configure PKCE instead of device authorization:\n\n[code example]"
    },
    {
      "id": "sign-in-with-the-cli",
      "title": "Sign in with the CLI",
      "role": "content",
      "text": "The `ff auth` commands handle login, session inspection, and token retrieval:\n\n[code example]\n\nPick a login method based on your environment:\n\n- **device-code** for headless terminals or shared shells where a local browser callback can't be reached.\n- **authorization_code_pkce** when a local browser callback works (typical for developer desktops).\n\nFeature Form lists the methods in `auth.oidcCLILoginMethods`. The CLI selects the first advertised method it supports unless you pass `--login-method`. It doesn't retry another method when the IdP rejects the selected method.\n\nUsername and password login is not recommended for production environments. For production automation, use a service account instead of storing a user's password.\n\nCLI sessions are stored in the CLI's local config (per profile). To skip interactive login entirely, set `FEATUREFORM_TOKEN` to a valid access token, or configure a service account with client credentials (see [Service accounts and machine credentials](#service-accounts-and-machine-credentials))."
    },
    {
      "id": "troubleshoot-cli-login",
      "title": "Troubleshoot CLI login",
      "role": "errors",
      "text": "Inspect the authentication metadata provided by Feature Form through a reachable REST endpoint:\n\n[code example]\n\nIf the REST API isn't public, port-forward the REST API service and query `http://localhost:8080/api/v1/auth/metadata` instead:\n\n[code example]\n\nConfirm that `cli_client_id`, `supported_login_methods`, `scopes`, and `audience` match the IdP client. For PKCE, also confirm `cli_redirect_uri`. Then inspect the IdP discovery document:\n\n[code example]\n\nFor device authorization, the document must contain `device_authorization_endpoint`. For PKCE, it must contain `authorization_endpoint` and `token_endpoint`, and have SHA-256 support in `code_challenge_methods_supported` when that field is present.\n\n| Symptom | Check and action |\n| --- | --- |\n| `invalid_client` | Confirm `auth.oidcCLIClientID` exactly matches the IdP client. For a public client, turn off client authentication. For confidential PKCE, supply the current secret at login. |\n| `unauthorized_client` | Enable the grant required by the selected method: Device Authorization Grant for `device_code`, or Authorization Code Grant with PKCE for `authorization_code_pkce`. |\n| Feature Form doesn't show the requested login method | Set `auth.oidcCLILoginMethods` to a method supported by both the CLI and IdP, upgrade the release, and inspect the Feature Form metadata again. |\n| Redirect URI mismatch | Make `auth.oidcCLIRedirectURI` exactly match a redirect URI registered for the IdP client, including its scheme, host, port, path, and query parameters. |\n| `invalid_scope` | Allow every value in `auth.oidcCLIScopes` at the IdP, or remove unsupported optional scopes. |"
    },
    {
      "id": "rbac",
      "title": "RBAC",
      "role": "content",
      "text": ""
    },
    {
      "id": "built-in-roles",
      "title": "Built-in roles",
      "role": "content",
      "text": "Feature Form has five built-in RBAC roles. The role ID in the left column is the literal string used in `ff rbac grant`.\n\nEach built-in role is a fixed set of finer-grained permissions—the underlying checks the authorization service runs on each request. For the full permission catalog, see [Reference > Permissions](https://redis.io/docs/latest/develop/ai/featureform/reference#permissions).\n\n| Role ID | Scope | What it grants |\n| --- | --- | --- |\n| `viewer` | Workspace | Read-only access to workspace metadata, the resource graph, the catalog, providers, and serving metadata. |\n| `operator` | Workspace | Everything a viewer has, plus writing providers, planning and applying changes, reading served features, and controlling scheduler workflows. |\n| `workspace_admin` | Workspace | Full administration of a single workspace—membership, audit, updates, deletion—plus everything an operator has. |\n| `global_admin` | Global | Workspace creation, plus full administration across every workspace in the deployment. |\n| `model` | Resource-constrained | Read access to a specific set of feature views, training sets, and serving data—nothing else. Used for model-team service accounts. |"
    },
    {
      "id": "role-scopes",
      "title": "Role scopes",
      "role": "content",
      "text": "Every role applies at a defined breadth—deployment-wide, a single workspace, or a specific set of resources within a workspace. Feature Form has three scopes, and each role works at exactly one:\n\n- **Global** — Deployment-wide actions, such as creating workspaces. Only `global_admin` operates at this scope.\n- **Workspace** — Actions inside a single workspace: providers, secret providers, apply, graph, catalog, serving metadata, and audit. A binding at this scope applies to one workspace only — grant the role again on each workspace a user needs.\n- **Resource-constrained** — A narrower form of workspace scope that limits a binding to a specific set of resources. Used for the `model` role, which only sees serving and training-set reads for the resources it was bound to.\n\nA binding pairs a role with a scope and a user, group, or service account. For example: \"Alice has `workspace_admin` on workspace `7f2e4d8c-…`\" or \"the `payments-team` group has `global_admin`.\""
    },
    {
      "id": "provision-the-first-global-admin",
      "title": "Provision the first global admin",
      "role": "content",
      "text": "A fresh Feature Form deployment has no role bindings in its database. There is no dedicated Helm value for an initial admin, so plan your IdP claim mapping before installing. To get the first global admin in place, choose one of two paths:\n\n**Map an IdP claim to `global_admin` (recommended for production).** Configure your IdP to issue a `featureform_roles` claim that contains `global_admin` for the appropriate user or group. Feature Form treats those tokens as global admin without a database binding, so the first admin can sign in and start granting roles to others immediately.\n\n**Bind manually after the first login.** A user with no role can still authenticate; they just can't do anything yet. From a host that already has an access token for a privileged account, run:\n\n[code example]\n\nThis option requires that *some* identity already has `global_admin`, which makes it suitable only for redirecting access from a temporary IdP-claim admin to a database-bound one, or for environments where you can run `ff` commands with a bootstrap token issued out-of-band."
    },
    {
      "id": "service-accounts-and-machine-credentials",
      "title": "Service accounts and machine credentials",
      "role": "content",
      "text": "Non-human identities—CI runners, model-serving processes, batch jobs—authenticate with a service account that holds a public key registered with Feature Form. Feature Form supports Ed25519 keys.\n\nThe end-to-end setup:\n\n1. Register a service-account principal in your IdP.\n2. Create a machine credential that registers the principal's public key with Feature Form.\n3. Grant the service account a workspace role.\n4. Use the resulting token in your automation.\n\nCreate a credential for a service account inside a workspace:\n\n[code example]\n\nThe `ff machine-credential` command also has subcommands for `list`, `get`, `rotate`, `revoke`, and `usage` (for audit-style usage records). All of them require the `machine_credential.write` or `machine_credential.read` permission on the target workspace.\n\nGrant the service account a workspace role the same way you would a user — use `--service-account <id>` instead of `--user <id>`:\n\n[code example]"
    },
    {
      "id": "audit",
      "title": "Audit",
      "role": "content",
      "text": "Feature Form records authorization-relevant events in an audit log. List events with:\n\n[code example]\n\nUseful filters:\n\n- `--workspace <id>` — scope to one workspace.\n- `--global` — only deployment-scoped events. Requires `global_admin`.\n- `--principal-id <id>` — events for a specific user, group, or service account.\n- `--event-type <type>` — filter by event name.\n\nCommon event types include:\n\n- `workspace.create`, `workspace.update`, `workspace.delete`\n- `rbac.grant`, `rbac.revoke`\n- `provider.write`, `secret_provider.write`\n- `apply.write`\n- `machine_credential.create`, `machine_credential.rotate`, `machine_credential.revoke`\n\nEach event includes the scope, workspace ID (if applicable), actor ID, event type, and creation timestamp. Reading the log requires the `audit.read` permission; deployment-scope reads additionally require `global_admin`."
    },
    {
      "id": "next-steps",
      "title": "Next steps",
      "role": "content",
      "text": "- [Deploy](https://redis.io/docs/latest/operate/featureform/deploy) — chart acquisition and install with OIDC enabled.\n- [Manage workspaces](https://redis.io/docs/latest/develop/ai/featureform/manage-workspace) — create workspaces, grant roles, and verify bindings.\n- [Register providers](https://redis.io/docs/latest/develop/ai/featureform/register-providers) — connect Postgres, Redis, S3, and other backends after auth is set up.\n- [Concepts](https://redis.io/docs/latest/develop/ai/featureform/concepts) — background on workspaces and the resource graph.\n- [Quickstart](https://redis.io/docs/latest/develop/ai/featureform/quickstart) — end-to-end verification."
    }
  ],
  "examples": [
    {
      "id": "configure-oidc-at-deploy-time-ex0",
      "language": "yaml",
      "code": "auth:\n  enabled: true\n  oidcIssuerURL: \"https://idp.example.com/realms/featureform\"\n  oidcClientID: \"featureform-api\"\n\n  oidcCLIClientID: \"featureform-cli\"\n  oidcCLIScopes: \"openid profile offline_access\"\n  oidcCLILoginMethods: \"device_code\"\n  deploymentID: \"acme-featureform-prod-us-west-2\"",
      "section_id": "configure-oidc-at-deploy-time"
    },
    {
      "id": "register-a-cli-client-ex0",
      "language": "yaml",
      "code": "auth:\n  oidcCLIClientID: \"featureform-cli\"\n  oidcCLILoginMethods: \"authorization_code_pkce\"\n  oidcCLIRedirectURI: \"http://localhost:8080/callback\"",
      "section_id": "register-a-cli-client"
    },
    {
      "id": "sign-in-with-the-cli-ex0",
      "language": "bash",
      "code": "# Interactive login. Uses the first method advertised by Feature Form.\nff auth login\n\n# Force a specific flow.\nff auth login --login-method device_code\nff auth login --login-method authorization_code_pkce\n\n# Inspect the current session.\nff auth status\nff auth whoami\n\n# Print the active access token (for use in tools that don't\n# integrate with the CLI session).\nff auth token\n\n# Clear the local session. Does not revoke tokens on the IdP.\nff auth logout",
      "section_id": "sign-in-with-the-cli"
    },
    {
      "id": "troubleshoot-cli-login-ex0",
      "language": "bash",
      "code": "curl --fail --silent --show-error \\\n  https://api.example.com/api/v1/auth/metadata | python3 -m json.tool",
      "section_id": "troubleshoot-cli-login"
    },
    {
      "id": "troubleshoot-cli-login-ex1",
      "language": "bash",
      "code": "kubectl --namespace <namespace> port-forward \\\n  service/featureform-featureform-rest 8080:8080",
      "section_id": "troubleshoot-cli-login"
    },
    {
      "id": "troubleshoot-cli-login-ex2",
      "language": "bash",
      "code": "curl --fail --silent --show-error \\\n  https://idp.example.com/realms/featureform/.well-known/openid-configuration \\\n  | python3 -m json.tool",
      "section_id": "troubleshoot-cli-login"
    },
    {
      "id": "provision-the-first-global-admin-ex0",
      "language": "bash",
      "code": "ff rbac grant global_admin --global --user <user-principal-id>",
      "section_id": "provision-the-first-global-admin"
    },
    {
      "id": "service-accounts-and-machine-credentials-ex0",
      "language": "bash",
      "code": "ff machine-credential create ci-runner-key \\\n  --workspace <workspace-id> \\\n  --service-account <service-account-principal-id> \\\n  --public-key \"<key material>\" \\\n  --algorithm Ed25519",
      "section_id": "service-accounts-and-machine-credentials"
    },
    {
      "id": "service-accounts-and-machine-credentials-ex1",
      "language": "bash",
      "code": "ff rbac grant operator \\\n  --workspace <workspace-id> \\\n  --service-account ci-runner",
      "section_id": "service-accounts-and-machine-credentials"
    },
    {
      "id": "audit-ex0",
      "language": "bash",
      "code": "ff audit list \\\n  --workspace <workspace-id> \\\n  --event-type workspace.delete \\\n  --page-size 50",
      "section_id": "audit"
    }
  ]
}
