Role-Based Access Control (RBAC)
In MLflow, admins grant users permissions by assigning them roles.
A role is a named group of permissions defined once and used multiple times.
With workspaces (--enable-workspaces), roles become workspace-scoped.
Prerequisite
Authentication enabled (mlflow server --app-name basic-auth).
For setting up authentication and managing users (login, signup, admin status, password updates), see Username and Password; this page picks up once users exist.
Concepts
Three entities - users, roles, resources - connected by two relations: role assignment and permission grants. Roles (and the grants they carry) are workspace-scoped when workspaces are enabled; see Workspace isolation for the on/off delta.
- User - an actor who wants to access resources.
- Resource - a target entity in MLflow that permissions are managed on, such as an experiment, a registered model, a prompt, a scorer, or an AI Gateway resource (a secret, an endpoint, or a model definition).
- Role - a named bundle of permissions on resources.
Roles
A role is a named list of (resource_type, resource_pattern, permission) grants.
Grants can target a single resource (experiment:42) or every resource of
a type (experiment:*), so one role can express both fine-grained and broad
access. For example, an editor role might carry (experiment, *, EDIT) to
let its members edit every experiment, while a narrower role could combine
(experiment, 42, READ) with (prompt, 7, EDIT).
Permission levels
The grantable permission levels for resource-scoped permissions are:
| Permission | Can read | Can use | Can update | Can delete | Can manage |
|---|---|---|---|---|---|
READ | Yes | No | No | No | No |
USE | Yes | Yes | No | No | No |
EDIT | Yes | Yes | Yes | No | No |
MANAGE | Yes | Yes | Yes | Yes | Yes |
USE is for consuming a resource without modifying it (invoking a gateway
endpoint, referencing a model definition, creating new experiments /
registered models within a workspace).
NO_PERMISSIONS is the resolver's deny sentinel for workspace-boundary
cases - e.g., a user with no workspace membership when
grant_default_workspace_access is off. It is not grantable as a role
permission or a direct grant; the auth server rejects attempts to assign
it. The effective permission when no role grant matches is the server's
default_permission (READ by default; see
Permission resolution).
Because grants are folded via a max (see Permission resolution), RBAC has no explicit-deny override. To restrict access to a specific resource, grant access more narrowly (per-resource or resource-type wildcard) rather than holding a broad workspace-level grant and trying to except the resource.
Permission resolution
For each authorization check, MLflow evaluates the user's effective permission as follows:
- Platform Admin bypass:
is_admin = trueshort-circuits to allow. - Role-derived grants: all of the user's roles in the request's workspace
contribute. Grants that apply to the resource fold via a max, and the
highest-priority permission wins (
MANAGE>EDIT>USE>READ).(workspace, *, MANAGE)folds into every per-resource check - it grants management of every resource in the workspace.(workspace, *, USE)does not fold into per-resource checks; it confers workspace membership and create rights only (see Workspace isolation). - Server
default_permission(defaults toREAD): a floor max'd into the resolution result. When no role grant matches at all, the floor is the final answer. The floor does not liftNO_PERMISSIONS- when the resolver returns it for a workspace-boundary reason (no workspace membership, missing workspace, lookup error), that deny stands. See Workspace isolation for when the floor applies in multi-workspace mode.
Workspace isolation
A workspace is an
isolated container for MLflow resources. Workspaces are off by default. To
turn them on, start the server with --enable-workspaces. Once enabled,
every experiment, registered model, prompt, scorer, and AI Gateway resource
belongs to exactly one workspace, and nothing crosses the boundary.
Roles and permissions follow the same boundary. A role named editor in
workspace foo is a completely separate row from editor in workspace
bar, and a permission grant made in one workspace has no effect in any
other. How that boundary shows up depends on whether multi-workspace
support is enabled.
Default (no --enable-workspaces). A single reserved default
workspace exists. All roles, all assignments, all permissions live there.
The server's default_permission config (when set) applies as a floor
for any (user, resource) pair.
Multi-workspace (--enable-workspaces). Named workspaces become
first-class. The home page renders a workspace switcher; the admin UI gets
a per-workspace entry point (/admin/ws?workspace=<name>); roles must be
created in a specific workspace. The server's default_permission applies
as the floor in named workspaces only when grant_default_workspace_access
is set; otherwise the effective floor in each named workspace is deny.
Two grants on the special (resource_type='workspace', resource_pattern='*') slot define workspace-wide access:
USEon(workspace, *)- the Workspace Member grant.- Confers workspace membership: the user can list and enter the workspace.
- Allows creating new resources (e.g., experiments, registered models) within it.
- Does not itself grant per-resource access. Read access on
individual resources comes from the server
default_permissionfloor (defaults toREAD); higher tiers require an explicit per-resource or resource-type wildcard grant.
MANAGEon(workspace, *)- the Workspace Manager grant. Full authority within the workspace, including creating roles, granting permissions, and managing role assignments. Cannot perform system-wide operations such as deleting users.
The seeded admin and user roles each carry one of these grants; see
Default roles.
User tiers
The admin UI uses Platform Admin for the first tier and Workspace Manager for the second; the doc uses the same labels.
| Tier | How it's expressed | Capability |
|---|---|---|
| Platform Admin | is_admin = true on the user row | Unrestricted system-wide. Sole bearer of user delete and bulk operations. |
| Workspace Manager | Holds (workspace, *, MANAGE) via any role | Full authority within those workspaces; can manage roles, users, and grants. |
| Regular user | Any other authenticated identity | No admin UI access; authorization flows through role-derived permissions only. |
Direct grants without a role
For the one-user one-resource case, you don't need to author a single-user
role. Each user has a reserved per-user role that holds their direct
grants; the grant_user_permission / revoke_user_permission APIs and the
admin UI's Direct permissions section write to it directly. From an
operator's perspective it's one call or one form field - the reserved role
is an implementation detail you never touch.
Admin UI: /admin → Users → click the user → Edit access → in
the Direct permissions section, add the (resource, permission) grant →
review → apply.
API:
auth_client.grant_user_permission("alice", "experiment", "42", "EDIT")
grant_user_permission is gated by per-resource MANAGE on the target
resource - the same authority check the legacy
create_experiment_permission() family used - so an experiment owner who
holds (experiment, 42, MANAGE) can grant access without holding
workspace-wide MANAGE. The role API itself requires workspace MANAGE or
Platform Admin, since authoring a role is an administrative action.
For reads, the same family includes
auth_client.get_user_permission(username, resource_type, resource_id) -
returns the resolved effective permission via the same resolver the
runtime authorization check uses - and
auth_client.list_user_permissions(username), which returns every grant
the user holds across all their roles, enriched with role_id /
role_name / workspace.
Common scenarios
The big shift from the legacy permission model: every "give X access to Y"
used to be a one-off per-resource POST. Now you build the permission set
once as a role and assign users to it. Both the admin UI and the
AuthServiceClient go through the same role-based primitives.
The examples below assume multi-workspace mode and that you've
authenticated as a Platform Admin or as a Workspace Manager of ml-research.
from mlflow.server import get_app_client
tracking_uri = "http://localhost:5000"
auth_client = get_app_client("basic-auth", tracking_uri=tracking_uri)
1. Give Alice EDIT on experiment 42
- Admin UI
- Role API
- Navigate to
/admin→ Roles. - Click Create role.
- In Permissions, add
experiment:42 → EDIT. - In Assigned users, add
alice. - Click Create.
role = auth_client.create_role(name="exp-42-editor", workspace="ml-research")
auth_client.add_role_permission(
role_id=role.id,
resource_type="experiment",
resource_pattern="42",
permission="EDIT",
)
auth_client.assign_role(username="alice", role_id=role.id)
2. Give a team READ access to every experiment in a workspace
A wildcard pattern lets the role apply to resources that don't exist yet; adding a new experiment automatically inherits the grant.
- Admin UI
- Role API
- Navigate to
/admin→ Roles. - Click Create role.
- In Permissions, add resource type
experiment, pattern*(rendered as "All experiments"), permissionREAD. - In Assigned users, add
alice,bob,carol. - Click Create.
The shape is identical to Scenario 1 with two differences:
resource_pattern="*" and a loop over the team.
role = auth_client.create_role(name="experiment-reader", workspace="ml-research")
auth_client.add_role_permission(
role_id=role.id,
resource_type="experiment",
resource_pattern="*",
permission="READ",
)
for member in ("alice", "bob", "carol"):
auth_client.assign_role(username=member, role_id=role.id)
3. Make a user a Workspace Manager
Every newly created workspace is seeded with a default admin role (and a
user role; see Default roles). Promoting a user means
assigning them the seeded admin role for that workspace. The role
explicitly carries (workspace, *, MANAGE).
- Admin UI
- Role API
- Navigate to
/admin→ Users. - Click
alice. - Click Edit access.
- In Role assignments, add
ml-research/admin. - Review changes → apply.
# The seeded `admin` role lives in the workspace; look it up by name.
admin_role = next(r for r in auth_client.list_roles("ml-research") if r.name == "admin")
auth_client.assign_role(username="alice", role_id=admin_role.id)
4. Onboard a team of N users with the same access
Create one role with desired permissions or reuse the seeded <workspace>/user role.
- Admin UI - from the role
- Admin UI - from each user
- Role API
- Navigate to
/admin→ Roles → click the role. - Click Edit role.
- In Assigned users, add all the users.
- Review → apply.
- Navigate to
/admin→ Users → click the user. - Click Edit access.
- In Role assignments, add the role → apply.
- Repeat for each user.
One round trip per user end-to-end. Pick whichever fits the operator's mental model; the result is identical.
Loop over the team and call assign_role once per member:
user_role = next(r for r in auth_client.list_roles("ml-research") if r.name == "user")
for member in ("alice", "bob", "carol"):
auth_client.assign_role(username=member, role_id=user_role.id)
Admin UI
The admin UI is the operator-facing surface for everything above. It's reached from two entry points:
- Platform Admins (
is_admin = true) navigate to/adminvia the sidebarManageentry. The page renders cross-workspace: every user in the system, every role in every workspace. - Workspace Managers click a gear icon on the home-page workspaces table
next to a workspace they administer. The link lands at
/admin/ws?workspace=<name>, the per-workspace view scoped to roles and users they can see.
Both views share the same layout: a Users tab and a Roles tab.
Managing Users

The Users tab lists every user with their visible roles. Click a username to open the user detail page, then Edit access to manage that user's roles, direct permissions, and admin status (Platform Admins only). Changes are previewed in a Review step before they're applied.

Managing Roles
The Roles tab lists roles in the active scope (all roles for Platform Admins; the active workspace's roles for Workspace Managers). Create role opens a single-page form with three sections (Role details, Permissions, Assigned users) and submits them in one shot. Edit role on the role detail page mirrors the same shape.


Default roles
When MLFLOW_RBAC_SEED_DEFAULT_ROLES is on (it is, by default), MLflow seeds
two roles into every newly created workspace:
| Role | Permission | Intent |
|---|---|---|
admin | (workspace, *, MANAGE) | Workspace Manager. Full authority within the workspace. |
user | (workspace, *, USE) | Workspace Member. Reads every resource in the workspace; can create experiments and registered models. |
The user who creates the workspace is automatically assigned the seeded admin
role for that workspace.
To disable the seeding (for installations that prefer to define roles manually):
export MLFLOW_RBAC_SEED_DEFAULT_ROLES=false
Migrating from the legacy permission model
If you're upgrading from a release before MLflow 3.13 that used the
per-resource permission endpoints, the auth-store backfill migration
translates the legacy permission tables into role_permissions rows.
Existing grants survive the upgrade - only the API surface changes.
The wire surface and AuthServiceClient shape change as follows:
| Pre-RBAC surface | Status | Replacement |
|---|---|---|
Per-resource permission REST endpoints + AuthServiceClient methods (create_experiment_permission(), etc.) | Removed | grant_user_permission / revoke_user_permission for one-off direct grants, or the role API for shared sets. |
Workspace permission REST endpoints + AuthServiceClient methods (set_workspace_permission(), etc.) | Removed | The role API: assign the seeded admin / user role, or author a role carrying (workspace, *, ...). |
At the wire level, the ~24 legacy per-resource REST endpoints consolidated
into four under /mlflow/users/permissions/*: grant, revoke, get,
list.
There is no deprecation-warning window: basic-auth was still marked experimental, so the carry cost of dozens of deprecation-warning-emitting methods outweighed the soft-transition benefit. After the upgrade, the one-user one-resource workflow is:
# Replace this:
# auth_client.create_experiment_permission(experiment_id, username, "EDIT")
auth_client.grant_user_permission(username, "experiment", experiment_id, "EDIT")
Per-resource MANAGE retains delegation. A user with (experiment, 42, MANAGE) can still grant other users access to experiment 42 - the new
grant_user_permission / revoke_user_permission are gated by the same
per-resource MANAGE check the legacy endpoints used.
Rollback
The legacy permission tables (experiment_permissions,
registered_model_permissions, the four gateway_*_permissions,
scorer_permissions, workspace_permissions) remain on disk for rollback
safety through at least one full release cycle. To revert the auth-store
schema:
mlflow db downgrade <previous-revision> --backend-store-uri <auth-db-uri>
The downgrade leaves the legacy tables in place and removes the
role_permissions rows the backfill produced. Re-running the upgrade
re-applies the backfill. The legacy tables are dropped in a subsequent
migration after one full release cycle, so plan upgrade timing accordingly.
API reference
The full list of role/permission/user-assignment methods and their signatures lives in the auto-generated API docs: