Azure DevOps offers a rich set of fine-grained permissions. Unfortunately, the user interface only shows a few of them. When granting permissions through the user interface, it is hard to understand what permissions actually take effect, and how to limit the set to the smallest group possible to enable a task. The Azure DevOps extension for the Azure CLI allows us to open up the hood and take advantage of the full range of fine-grained permissions defined in Azure DevOps.
Azure DevOps Permission System
There are a few concepts to juggle when working with Azure DevOps permissions: subjects, permission namespaces, and security tokens.
- Subjects are either users or groups, and you can grant permissions directly to either type.
- Permission namespaces are fundamentally named bit fields that represent individual permissions. These bits can be turned on, off, or unset for a particular subject. Azure DevOps evaluates these to allow or deny a specific action that a user might attempt.
- Security tokens are arbitrary strings that represent resources in Azure DevOps.
There is no concept of a role or a role assignment as in Azure RBAC. Instead, permissions are applied directly to a group or a user. To create your own pseudo-role with fine-grained permissions, start by creating a group then adding users or existing groups to the new group. To assign permissions to the new group, you need the group’s identifier. Furthermore, you need the security namespace identifier, the security token in that namespace, and which bits in the namespace correspond with the permissions you want. These are quite a few concepts coming together, so let’s step through an example.
Granting permission to manage Service Connections
In this example, we’ll imagine that my team enforces strict segregation of duty policies and wants to minimize the number of users who handle credentials for Service Connections. Simultaneously, the team wants to ensure that the users who manage Service Connection credentials have only that permission and cannot configure Azure DevOps in other ways. We will allow these users to set credentials on a Service Connection but not create or use them.
First, create a group for the users that will have this role. This task can be performed in the UI, but I’ll show you how to do it with the Azure DevOps extension for the Azure CLI. (You will need to install this extension to follow these steps. See instructions here: https://docs.microsoft.com/en-us/azure/devops/cli/?view=azure-devops). After making sure to login with “az login,” I created the “Service Connection Managers” group with the following command:
az devops security group create \
--organization https://dev.azure.com/olive-steel \
--project scheduling \
--scope project \
--name "Service Connection Managers" \
--description "Manage Service Connection Credentials" \
--query "{displayName:displayName,descriptor:descriptor}" \
--output table
To successfully run this command, we need to provide the organization URL, the project name, and whether to define the group at the project level or the organization level. In this case, I wanted to create the group at the project level. Besides the group’s name, I also provided a description for the group. On success, we can see that DevOps created the group I requested and assigned it a descriptor. This descriptor will be necessary for later commands:
DisplayName | Descriptor |
Service Connection Managers | vssgp.Uy0xLTktMTU1MTM3NDI0NS0x… |
Next, I need to find the security namespace with the permissions I am interested in. A few namespaces are documented online, but the best way to get the full set is to query them with the CLI. The command is simple enough, but like all commands, it requires the organization to query. Entering the organization and project for every command is tedious, so the CLI provides an option to set defaults:
az devops configure \
--defaults organization=https://dev.azure.com/olive-steel project=scheduling
Now I can query the security namespaces without adding the organization to the command:
az devops security permission namespace list \
--output table
The output is very long and returned 58 namespaces for my example organization. A search through the list finds one likely namespace: ServiceEndpoints with the namespace identifier: 49b48001-ca20-4adc-8111-5b60c903a50c. “Service Endpoints” is an alternate name for the feature now labeled “Service Connections” in the user interface. We can use this namespace GUID to pull up the details for the namespace.
az devops security permission namespace show \
--id 49b48001-ca20-4adc-8111-5b60c903a50c \
--query "[0].actions[].{bit:bit,displayName:displayName}" \
--output table
The results show us the individual permission bits, summarized below:
Bit | DisplayName |
1 | Use Service Connection |
2 | Administer Service Connection |
4 | Create Service Connection |
8 | View Authorization |
16 | View Service Connection |
This information will be handy when we update permissions. Before we can update permissions, we need to figure out the security token. A security token in Azure DevOps is an arbitrary string representing a resource in Azure DevOps. After some experimentation, I learned that Azure DevOps would create security tokens after making its first Service Connection. As far as I know, security tokens or their patterns aren’t documented anywhere, nor do I know of a CLI command to list them.
After creating a service connection, the CLI shows us some security tokens, and we can learn about their patterns.
az devops security permission list \
--subject ${GROUP_DESCRIPTOR} \
--id ${NAMESPACE_ID} \
--query "[].{token:token}" \
--output table
Note that I have replaced some of the longer values with variables for brevity. This command lists all tokens and related permissions in a specific namespace for the requested group. We provide it with two of the values we found earlier: the group descriptor and the security namespace id. The CLI shows the following security tokens:
Token |
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e |
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71 |
endpoints/Collection/ba349990-dc9c-4bf8-9340-70845950fd71 |
Security tokens vary by security namespace, so what we learn about values in the ServiceEnpoints namespace may not apply to other namespaces. The GUID values represent the project ID and the Service Connection ID, respectively. The Azure CLI can confirm these values for us by listing the project ID:
az devops project list \
--query "value[].{name:name,ID:id}" \
--output table
This confirms the project ID is the GUID starting with “80ca”:
Name | ID |
scheduling | 80cad8fd-1891-4491-95d8-cc68f0f8b72e |
And next, the Service Connection ID.
az devops service-endpoint list \
--query "[].{name:name,ID:id}" \
--output table
This confirms the Service Connection ID is the GUID starting with “ba34”:
Name | ID |
Service Connection One | ba349990-dc9c-4bf8-9340-70845950fd71 |
So, interpret each token according to the following table:
Token | Scope |
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e | All Service Connections in the “scheduling” project |
endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71 | “Service Connection One” in the “scheduling” project |
endpoints/Collection/ba349990-dc9c-4bf8-9340-70845950fd71 | “Service Connection One” in any(?) project |
The first token covers all Service Connections in the project. The second token covers just one Service Connection in the project. Because these two tokens share a common prefix, we might infer that tokens are matched by a prefix. In earlier experiments, I found that permissions for the token “endpoints” would apply to any Service Connection, supporting that hypothesis. The token “endpoints/Collection/ba34…” might grant permissions on the Service Connection anywhere in the Azure DevOps project collection. Still, I have not experimented with permissions across several projects yet, so I cannot be sure.
In any case, we can continue our example with the tokens we have available by granting permissions to our group using the narrowest scope. The second security token grants permission on “Service Connection One” in the “scheduling” project, and I’ll use that one.
To build the permission update command, combine the security namespace identifier for the ServiceEndpoints namespace, the group descriptor for the Service Connection Managers group, and the selected security token. Also, calculate the bitmasks for the permissions to allow and deny. To calculate the bitmask, refer back to the permission bits table for the ServiceEnpoints namespace:
Bit | DisplayName |
1 | Use Service Connection |
2 | Administer Service Connection |
4 | Create Service Connection |
8 | View Authorization |
16 | View Service Connection |
In this table, each permission is represented by a number. To create the mask for the permissions you are interested in, add the corresponding numbers together. For example, I decided to give my new group permission to Administer and View Service Connections. The corresponding permission values are 16+8+2=26. This becomes the argument for the “allow-bit” parameter on the CLI. In this example, I decided to deny the “Use” and “Create” permissions. The corresponding value for the “deny-bit” is 4+1=5.
az devops security permission update \
--deny-bit 5 \
--allow-bit 26 \
--namespace-id ${NAMESPACE_ID} \
--subject ${GROUP_DESCRIPTOR} \
--token "endpoints/80cad8fd-1891-4491-95d8-cc68f0f8b72e/ba349990-dc9c-4bf8-9340-70845950fd71" \
--output table
The output from the command shows the resulting permissions:
Name | Bit | Permission Description | Permission Value |
Use | 1 | Use Service Connection | Deny |
Administer | 2 | Administer Service Connection | Allow |
Create | 4 | Create Service Connection | Deny |
ViewAuthorization | 8 | View Authorization | Allow |
ViewEndpoint | 16 | View Service Connection | Allow |
Testing the Permissions
I want to try these permissions to be sure that they work as I expect them to. My default user owns the organization and all projects in it. I am already allowed Use, Administer, and Create:
Name | Bit | Permission Description | Permission Value |
Use | 1 | Use Service Connection | Allow (inherited) |
Administer | 2 | Administer Service Connection | Allow (inherited) |
Create | 4 | Create Service Connection | Allow (inherited) |
ViewAuthorization | 8 | View Authorization | Not set |
ViewEndpoint | 16 | View Service Connection | Not set |
Since that includes two of the permissions I want to deny in the new group, I need to invite another user into the project. Fortunately, I have more than one Microsoft/Office365 user, so I simply added my alternate user to the organization and the scheduling project as a reader. As expected, my alternate user has no permissions related to Service Connections and cannot create new ones or view existing configurations.

Next, I added this secondary user to the Service Connection Managers group:
az devops security group membership add \
--member-id ${USER_EMAIL} \
--group-id ${GROUP_DESCRIPTOR} \
--output table
After this, I checked the new user’s permissions in the ServiceEndpoints namespace:
az devops security permission show \
--subject ${USER_EMAIL} \
--id ${NAMESPACE_ID} \
--token ${SECURITY_TOKEN} \
--output table
I saw the expected results:
Name | Bit | Permission Description | Permission Value |
Use | 1 | Use Service Connection | Deny (inherited) |
Administer | 2 | Administer Service Connection | Allow (inherited) |
Create | 4 | Create Service Connection | Deny (inherited) |
ViewAuthorization | 8 | View Authorization | Allow (inherited) |
ViewEndpoint | 16 | View Service Connection | Allow (inherited) |
As expected, the user can view the existing Service Connection.

As expected, the user fails when trying to create a Service Connection.

However, editing the existing service connection works!


Wrap Up
In this example, we could use the Azure DevOps CLI to find the permissions we needed. Once I determined the security namespace that interested me, I could use it to check for permissions assigned to an Azure DevOps group for specific scopes. However, if I had not found an example to work backward from, determining the correct Security Token may have been impossible. Security Tokens in Azure DevOps are very sparsely documented. Finding and figuring out the right pattern for a token is likely to be the most challenging task facing a team trying to implement fine-grained permissions in Azure DevOps. With the Azure DevOps CLI, some experimentation, and some luck, it is possible to deploy limited fine-grained permissions in Azure DevOps.