Managing Azure RBAC with the Azure CLI
The Azure CLI gives you precise, scriptable control over every aspect of RBAC management. Knowing the right commands and flags for listing, creating, and removing role assignments — and understanding the output — makes access management faster and less error-prone than using the portal alone. This page walks through a complete real-world RBAC workflow from start to finish.
Prerequisites
To follow the commands in this page, you need:
- The Azure CLI installed and authenticated (
az login) - The correct subscription set as default (
az account set —subscription “MY_SUBSCRIPTION_NAME”) - A role that can read role assignments at the scope you are querying — Reader is sufficient for listing, but you need User Access Administrator or Owner to create or delete assignments
All commands in this page use the az role assignment and az role definition command groups. The az ad commands are used for looking up identity object IDs from Microsoft Entra ID.
Listing role assignments
The most important thing to understand about listing role assignments is the difference between direct assignments and inherited assignments. Without the —include-inherited flag, you only see assignments created directly at the scope you specify.
List all assignments in the current subscription
az role assignment list --output tableOutput columns: Principal, Role, Scope. This gives a quick overview of who has what.
List assignments at a specific resource group, including inherited ones
az role assignment list \
--resource-group "production-app" \
--include-inherited \
--output tableThis shows both assignments made directly on production-app and assignments inherited from the subscription or management group level. Use this when you want to understand the complete access picture for a resource group.
List all assignments for a specific user
az role assignment list \
--assignee "developer@company.com" \
--all \
--output tableThe —all flag searches all subscriptions accessible to your account, not just the default subscription.
Filter the output to find high-privilege assignments
Use a JMESPath query to find only Owner and Contributor assignments at subscription scope:
az role assignment list \
--subscription "YOUR_SUBSCRIPTION_ID" \
--output json \
--query "[?scope == '/subscriptions/YOUR_SUBSCRIPTION_ID' && (roleDefinitionName == 'Owner' || roleDefinitionName == 'Contributor')]" \
| jq '[.[] | {principal: .principalName, role: .roleDefinitionName, scope: .scope}]'This is a useful audit command for finding the broadest and most sensitive assignments. These should be reviewed regularly. The principle of least privilege guide covers what to look for during an access review.
List role assignments for a service principal by app ID
# Get the service principal's object ID first
SP_OBJECT_ID=$(az ad sp show \
--id "00000000-0000-0000-0000-000000000000" \
--query id \
--output tsv)
# List their assignments
az role assignment list \
--assignee "$SP_OBJECT_ID" \
--all \
--output tableCreating role assignments
The az role assignment create command requires three inputs: the assignee, the role, and the scope. There are multiple ways to specify each.
Assigning a role to a user at resource group scope
az role assignment create \
--assignee "developer@company.com" \
--role "Contributor" \
--resource-group "staging-app"Using —resource-group is shorthand for providing the full scope ID. The CLI expands it to the full ARM resource ID for you.
Assigning a role using the full scope ID
az role assignment create \
--assignee "developer@company.com" \
--role "Reader" \
--scope "/subscriptions/aaaa-bbbb-cccc-dddd/resourceGroups/production-app"Assigning a role at subscription scope
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
az role assignment create \
--assignee "security-team@company.com" \
--role "Reader" \
--scope "/subscriptions/$SUBSCRIPTION_ID"Assigning a role to a group
First get the group’s object ID:
GROUP_OBJECT_ID=$(az ad group show \
--group "DevOps Team" \
--query id \
--output tsv)
az role assignment create \
--assignee-object-id "$GROUP_OBJECT_ID" \
--assignee-principal-type "Group" \
--role "Contributor" \
--resource-group "dev-infrastructure"When assigning to a group, use —assignee-object-id and —assignee-principal-type together. This avoids an extra identity lookup and prevents ambiguity.
Assigning a role to a managed identity
IDENTITY_OBJECT_ID=$(az identity show \
--name "myapp-identity" \
--resource-group "production-app" \
--query principalId \
--output tsv)
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type "ServicePrincipal" \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/aaaa-bbbb-cccc-dddd/resourceGroups/production-app/providers/Microsoft.Storage/storageAccounts/myprodstorage"Managed identity object IDs are accessed via principalId on the identity resource. The —assignee-principal-type is ServicePrincipal for both service principals and managed identities.
Complete workflow: check → assign → verify → remove
This section walks through the full lifecycle of a role assignment. The scenario: a new developer, newdev@company.com, needs Contributor access to the staging-webapp resource group.
Step 1: Check what the user currently has
az role assignment list \
--assignee "newdev@company.com" \
--all \
--include-inherited \
--output tableBefore creating a new assignment, check whether they already have access through inheritance or an existing direct assignment. You might discover they already have Contributor through a group assignment.
Sample output (showing no existing assignments):
Principal Role Scope
----------- ------ -----
(no results)Step 2: Look up the user’s object ID
USER_OBJECT_ID=$(az ad user show \
--id "newdev@company.com" \
--query id \
--output tsv)
echo "User object ID: $USER_OBJECT_ID"Step 3: Create the role assignment
az role assignment create \
--assignee-object-id "$USER_OBJECT_ID" \
--assignee-principal-type "User" \
--role "Contributor" \
--resource-group "staging-webapp" \
--output jsonThe command outputs the full role assignment JSON on success. You should see a response with principalId, roleDefinitionId, scope, and a id containing the assignment GUID. Save the assignment ID if you want to be able to delete this specific assignment later.
Sample output:
{
"id": "/subscriptions/aaaa-bbbb-cccc-dddd/resourceGroups/staging-webapp/providers/Microsoft.Authorization/roleAssignments/1234-5678-90ab-cdef",
"principalId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"principalType": "User",
"roleDefinitionId": "/subscriptions/aaaa-bbbb-cccc-dddd/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c",
"scope": "/subscriptions/aaaa-bbbb-cccc-dddd/resourceGroups/staging-webapp"
}Step 4: Verify the assignment was created
az role assignment list \
--assignee "newdev@company.com" \
--resource-group "staging-webapp" \
--output tableYou should now see the Contributor assignment on staging-webapp. Note that there is a short propagation delay (usually seconds, occasionally a minute or two) before the assignment takes effect across all Azure services.
Step 5: Test effective access (optional but recommended)
To verify the user can actually do what you intended, ask them to run a test command, or use the portal’s “Check access” feature on the resource group. From the CLI, you can also check effective permissions for a principal at a scope:
az role assignment list \
--assignee "newdev@company.com" \
--resource-group "staging-webapp" \
--include-inherited \
--output tableStep 6: Remove the assignment when no longer needed
Use the same parameters you used to create the assignment:
az role assignment delete \
--assignee "newdev@company.com" \
--role "Contributor" \
--resource-group "staging-webapp"Or delete by assignment ID (more precise when a user has multiple assignments at the same scope):
az role assignment delete \
--ids "/subscriptions/aaaa-bbbb-cccc-dddd/resourceGroups/staging-webapp/providers/Microsoft.Authorization/roleAssignments/1234-5678-90ab-cdef"After deletion, verify with another list command:
az role assignment list \
--assignee "newdev@company.com" \
--resource-group "staging-webapp" \
--output tableThe output should now be empty (or only show inherited assignments that remain).
Working with role definitions
You also need to query role definitions to find the right role for a given job. The az role definition command group handles this.
Find built-in roles related to a service
az role definition list \
--query "[?contains(roleName, 'Storage')].{Name:roleName, Description:description}" \
--output tableShow the full permission list for a specific role
az role definition list \
--name "Storage Blob Data Contributor" \
--output jsonThis shows all Actions, NotActions, DataActions, and NotDataActions for the role — essential when deciding whether a built-in role fits your needs or whether you need a custom role.
List custom roles in the current tenant
az role definition list \
--custom-role-only true \
--output tableFor creating and managing custom roles via the CLI, see built-in vs custom roles for the complete workflow including the JSON definition format.
Useful flags and formatting tips
A few flags are worth knowing for daily use:
Output formats
—output table— human-readable for quick visual inspection—output json— full structured output for scripting and debugging—output tsv— tab-separated, ideal for capturing a single value into a variable—output yaml— useful for reading nested structures more clearly than JSON
Querying with JMESPath
The —query flag accepts JMESPath expressions for filtering and reshaping output:
# Extract just the role names and scopes
az role assignment list \
--assignee "developer@company.com" \
--all \
--query "[].{Role:roleDefinitionName, Scope:scope}" \
--output tableCapturing subscription ID programmatically
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
TENANT_ID=$(az account show --query tenantId --output tsv)These are useful in scripts that need to construct full scope resource IDs without hardcoding GUIDs.
Add —output table as a default in your ~/.azure/config file to avoid typing it on every command during interactive sessions. In scripts, always specify the output format explicitly to ensure consistent parsing regardless of the user’s default configuration.
Common CLI mistakes when managing RBAC
- Forgetting —include-inherited when auditing. Without this flag, you see only direct assignments at the queried scope, missing any inherited permissions from higher scopes. An audit that misses inherited assignments gives you a false picture of effective access.
- Using —assignee with a display name instead of email or object ID. Display names are not unique — multiple people can share a display name. Always use email addresses, app IDs, or object IDs with the —assignee flag to ensure you are targeting the right principal.
- Not verifying the active subscription before creating assignments. If you have switched subscriptions recently, it is easy to create an assignment in the wrong subscription. Run
az account showto confirm which subscription is active before any create or delete operations. - Deleting assignments by role name instead of ID when duplicates exist. If a user has two Contributor assignments at the same scope (which can happen with group and direct assignments), deleting by role name deletes only one of them. Use the assignment ID for precision when you need to delete a specific assignment.
- Not scripting access reviews. Manual reviews in the portal miss assignments in subscriptions you forget to check. Schedule a regular
az role assignment list —alland pipe the output to a file or monitoring system to catch scope creep over time.
Summary
- Use
az role assignment list —include-inheritedfor complete visibility of effective access at any scope. - When creating assignments for groups or managed identities, use
—assignee-object-idand—assignee-principal-typetogether for precision and speed. - The full role assignment lifecycle is: list existing access → create assignment → verify → delete when no longer needed.
- Use
az role definition list —name “RoleName” —output jsonto inspect a role’s complete permission set before assigning it. - Capture IDs into shell variables using
—output tsvfor clean scripting without parsing JSON. - Always confirm the active subscription with
az account showbefore write operations.
Frequently asked questions
How do I find the object ID for a user when creating a role assignment?
Use az ad user show --id user@company.com --query id --output tsv. For service principals, use az ad sp show --id APP_ID --query id --output tsv. For managed identities, use az identity show --name IDENTITY_NAME --resource-group RG_NAME --query principalId --output tsv.
What is the difference between --assignee and --assignee-object-id in az role assignment create?
The --assignee flag accepts either an email address, a service principal app ID, or an object ID and does a lookup if needed. The --assignee-object-id flag takes only an object ID (GUID) and skips the lookup. In scripts and automation, --assignee-object-id is preferred because it is faster (one fewer API call), deterministic, and avoids ambiguity when the same email exists in multiple tenants.
How do I list role assignments for all subscriptions I have access to?
Use az role assignment list --all --include-inherited --output table. The --all flag queries across all subscriptions in the current tenant that you have access to. Without --all, the command only queries the currently active subscription set with az account set.