Azure-cli: az ad app grants not working or usable as expected

Created on 11 Feb 2020  ยท  12Comments  ยท  Source: Azure/azure-cli

Describe the bug
"az ad app permission grant" only seems to grant a single scope. If I call it successive times, the existing scope is overwritten. This behaviour is not clearly documented, nor is the way to grant. multiple scopes.

"az ad app permission admin-consent" appears to be non-functional. How can admin consent be granted without three key pieces of information, AppID, APIID, and Scope? What is this doing? How can a single scope be granted at the application level?

I would also consider it a bug that "az ad app permission grant" cannot be used to grant application type scopes.

To Reproduce
Try to execute the above commands. "az ad app permission admin-consent" is not documented in any way that makes sense. "az ad app permission grant" seems to work, but successive calls over-write your work.

Expected behavior
There should be a single command that allows you to grant individual (or comma separated lists) of scopes for an API to an app. That same command should have a flag for Delegated vs Application Type. It should not overwrite itself. Worst case, if there have to be two commands, the one used to grant admin-consent to the application type should allow appropriate parameters.

Documentation of these calls should be clear and concise.

Environment summary
Windows 10. Installed via downloaded MSI for x64. cmd.exe shell.

C:\Program Files\Microsoft SDKs\Azure.NET SDK\v2.9\bin>az --version
azure-cli 2.0.81

command-modules-nspkg 2.0.3
core 2.0.81
nspkg 3.0.4
telemetry 1.0.4

Python location 'C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\python.exe'
Extensions directory 'C:Users\dpomerantz.azure\cliextensions'

Python (Windows) 3.6.6 (v3.6.6:4cf1f54eb7, Jun 27 2018, 02:47:15) [MSC v.1900 32 bit (Intel)]

AAD

Most helpful comment

az ad app permission admin-consent is the old way of granting all Application Permissions and Delegated Permissions to an app at the same time. It calls an internal API: https://github.com/Azure/azure-cli/blob/1c0e9c3e18c34116d5a2e25cb7460d1af8a85969/src/azure-cli/azure/cli/command_modules/role/custom.py#L893

โš  It can only be called by a user, not a service principal.
โš  It fails in Cloud Shell, because main.iam.ad.ext.azure.com is an endpoint not supported by Cloud Shell.
โš  We will deprecate it in the future.

For example, we can call it with app's Application ID:

az ad app permission admin-consent --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a

Before:

After:

Grant Delegated Permissions

To grant Delegated Permissions, we recommend using az ad app permission grant. If multiple scopes are needed, separate them with spaces and surround the scopes with quotes: "a b". For example, to grant MS Graph (whose Application ID is 00000003-0000-0000-c000-000000000000) Delegated Permission to my app (Application ID 46eb4122-bd2b-4f54-af7b-6d79b46ee31a):

# Line breaks for legibility only, same for following commands
az ad app permission grant --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a 
                           --api 00000003-0000-0000-c000-000000000000
                           --scope "Directory.Read.All Directory.ReadWrite.All"

Grant Application Permissions

To grant Application Permissions, we can use any of these 2 APIs:

  • {principalId}/appRoleAssignments

    # URL contains principalId
    az rest --method POST 
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignments
            --body '{
              "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
              "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4",
              "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"
            }' 
    
  • {resourceId}/appRoleAssignedTo

    # URL contains resourceId
    az rest --method POST 
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignedTo
            --body '{
              "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
              "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4",
              "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"
            }' 
    

principalId (8aaa6158-7450-4407-ba08-61377f23d05f) is the objectId of the assigned client service principal. Service principal is also called Managed application in local directory or Enterprise Application.

You may get it from Azure Portal:

1

2

Or query principalId with Application ID:

> az ad sp show --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a --query "objectId" --output tsv
8aaa6158-7450-4407-ba08-61377f23d05f

resourceId (a3efc889-f1b7-4532-9e01-91e32d1039f4) is the objectId of the resource service principal. In this case it is for MS Graph's service principal. It varies in different tenants (directories). Retrieve it with MS Graph's Application ID:

$ az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "objectId" --output tsv
a3efc889-f1b7-4532-9e01-91e32d1039f4

appRoleId (19dbc75e-c2e2-444c-a770-ec69d8559fc7) is the ID of the permission. In this case, it is for Directory.ReadWrite.All. Retrieve it with MS Graph's Application ID:

> az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?value=='Directory.ReadWrite.All']"
[
  {
    "allowedMemberTypes": [
      "Application"
    ],
    "description": "Allows the app to read and write data in your organization's directory, such as users, and groups, without a signed-in user.  Does not allow user or group deletion.",
    "displayName": "Read and write directory data",
    "id": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
    "isEnabled": true,
    "value": "Directory.ReadWrite.All"
  }
]

You may also use F12 Developer Tools of the browser to capture network trace to check related ID conversions. (Quite complicated I admit.)

All 12 comments

Hi @jiasli , could you please help take a look at this ?

add to S166.

This was pushed to a future release. How many times is it going to get pushed before being addressed and resolved. Is any one able to comment on a commitment that this will be resolved with a definitive timeline?

az ad app permission admin-consent is the old way of granting all Application Permissions and Delegated Permissions to an app at the same time. It calls an internal API: https://github.com/Azure/azure-cli/blob/1c0e9c3e18c34116d5a2e25cb7460d1af8a85969/src/azure-cli/azure/cli/command_modules/role/custom.py#L893

โš  It can only be called by a user, not a service principal.
โš  It fails in Cloud Shell, because main.iam.ad.ext.azure.com is an endpoint not supported by Cloud Shell.
โš  We will deprecate it in the future.

For example, we can call it with app's Application ID:

az ad app permission admin-consent --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a

Before:

After:

Grant Delegated Permissions

To grant Delegated Permissions, we recommend using az ad app permission grant. If multiple scopes are needed, separate them with spaces and surround the scopes with quotes: "a b". For example, to grant MS Graph (whose Application ID is 00000003-0000-0000-c000-000000000000) Delegated Permission to my app (Application ID 46eb4122-bd2b-4f54-af7b-6d79b46ee31a):

# Line breaks for legibility only, same for following commands
az ad app permission grant --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a 
                           --api 00000003-0000-0000-c000-000000000000
                           --scope "Directory.Read.All Directory.ReadWrite.All"

Grant Application Permissions

To grant Application Permissions, we can use any of these 2 APIs:

  • {principalId}/appRoleAssignments

    # URL contains principalId
    az rest --method POST 
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignments
            --body '{
              "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
              "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4",
              "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"
            }' 
    
  • {resourceId}/appRoleAssignedTo

    # URL contains resourceId
    az rest --method POST 
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignedTo
            --body '{
              "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
              "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4",
              "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"
            }' 
    

principalId (8aaa6158-7450-4407-ba08-61377f23d05f) is the objectId of the assigned client service principal. Service principal is also called Managed application in local directory or Enterprise Application.

You may get it from Azure Portal:

1

2

Or query principalId with Application ID:

> az ad sp show --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a --query "objectId" --output tsv
8aaa6158-7450-4407-ba08-61377f23d05f

resourceId (a3efc889-f1b7-4532-9e01-91e32d1039f4) is the objectId of the resource service principal. In this case it is for MS Graph's service principal. It varies in different tenants (directories). Retrieve it with MS Graph's Application ID:

$ az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "objectId" --output tsv
a3efc889-f1b7-4532-9e01-91e32d1039f4

appRoleId (19dbc75e-c2e2-444c-a770-ec69d8559fc7) is the ID of the permission. In this case, it is for Directory.ReadWrite.All. Retrieve it with MS Graph's Application ID:

> az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?value=='Directory.ReadWrite.All']"
[
  {
    "allowedMemberTypes": [
      "Application"
    ],
    "description": "Allows the app to read and write data in your organization's directory, such as users, and groups, without a signed-in user.  Does not allow user or group deletion.",
    "displayName": "Read and write directory data",
    "id": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
    "isEnabled": true,
    "value": "Directory.ReadWrite.All"
  }
]

You may also use F12 Developer Tools of the browser to capture network trace to check related ID conversions. (Quite complicated I admit.)

Thanks for all of the detailed information! I'll take some time and digest this, and come back if I have any further questions on this. I understand a bit more on the delays though.

Hi @dmprantz , thanks a lot for your understanding. We have been working tightly with AAD team on this feature. Don't hesitate to let us know if there are any concerns.

For the granting app (or service principal, which you use to log in and grant permissions to another service principal),

  • Directory.ReadWrite.All is needed for granting Delegated Permissions
  • AppRoleAssignment.ReadWrite.All is needed for granting Application Permissions

โš  Keep in mind that AppRoleAssignment.ReadWrite.All is extremely privileged, as it allows granting any app-only permission, including RoleManagement.ReadWrite.Directory, which can then be used to give anyone (or any app) even higher privileges up to and including Company Administrator (i.e. Global Admin).

If you use az ad command, make sure the permission is given under Azure Active Directory Graph, since az ad internally uses AD Graph API. If you use az rest --uri https://graph.microsoft.com/, the permission should be given under Microsoft Graph.

Reference: https://winsmarts.com/how-to-grant-admin-consent-to-an-api-programmatically-e32f4a100e9d

Hi @jiasli ,using the az ad app permission grant command to grand Delegated Permission is giving us a 404 NotFound error (like #12797, but we are NOT using a Service Principal). In our case the Delegated Permission is from Microsoft Graph (Directory.ReadWrite.All). Can you please clarify HOW to grant Delegated Permissions programmatically?

I found a solution.
BEFORE invoking the az ad app permission grant, the Service Principal for your App Registration MUST exists (that's why you get the 404 NotFound!). So, invoking the az ad sp create --id <yourAppId> before the az ad app permission grant will fix everything.

This is really confusing and undocumented.

@fume, glad to know it is solved. You may use --debug to check which request failed and fix it accordingly. BTW, We will track MS Graph related issues at #12946.

Update

Service Principal API of Microsoft Graph is now GA. I have updated my answer https://github.com/Azure/azure-cli/issues/12137#issuecomment-596567479 to reflect the most recent changes.

The GA APIs have some changes from this blog I provided earlier for granting Application Permissions.

In short

  1. The subject of appRoleAssignments and appRoleAssignedTo are interchanged.
  2. Only follow the official document and don't use the unsupported combinations.

TL;DR

We can consider the assignment as a relationship/binding between 2 service principals. There are 4 ways to create an assignment.

In the blog, the APIs are

  • {resourceId}/appRoleAssignments
  • {principalId}/appRoleAssignedTo

Now the official APIs are

We can draw a diagram like this:

| Id | | API | Correctness
|-|-|-|-
| {principalId} | โžก | appRoleAssignments | (1) ๐ŸŸข Correct
| | โ†˜ | | (2) ๐ŸŸก Undocumented, unsupported
| | โ†— | | (3) ๐ŸŸก Undocumented, unsupported
| {resourceId} | โžก | appRoleAssignedTo | (4) ๐ŸŸข Correct

Ideally the same URL can be used for 4 operations:

  1. POST to create
  2. GET to list
  3. GET to show, by appending the id of the assignment
  4. DELETE to delete, by appending the id of the assignment

To use the above operations with URLs:

  • (1)(4): {principalId}/appRoleAssignments and {resourceId}/appRoleAssignedTo are the only official ways. All 4 operations are supported.
  • (2)(3): {resourceId}/appRoleAssignments and {principalId}/appRoleAssignedTo are undocumented and unsupported. They only work for an individual assignment (operation 1, 3, 4), but not GET to list (operation 2).

If we enumerate all combinations:

POST:

  • ๐ŸŸข POST {principalId}/appRoleAssignments
  • ๐ŸŸข POST {resourceId}/appRoleAssignedTo
  • ๐ŸŸก POST {principalId}/appRoleAssignedTo (Unsupported)
  • ๐ŸŸก POST {resourceId}/appRoleAssignments (Unsupported)

GET to list:

  • ๐ŸŸข GET {principalId}/appRoleAssignments
  • ๐ŸŸข GET {resourceId}/appRoleAssignedTo
  • ๐Ÿ”ด GET {principalId}/appRoleAssignedTo (Not return the desired result)
  • ๐Ÿ”ด GET {resourceId}/appRoleAssignments (Not return the desired result)

GET to show:

  • ๐ŸŸข GET {principalId}/appRoleAssignments/id
  • ๐ŸŸข GET {resourceId}/appRoleAssignedTo/id
  • ๐ŸŸก GET {principalId}/appRoleAssignedTo/id (Unsupported)
  • ๐ŸŸก GET {resourceId}/appRoleAssignments/id (Unsupported)

DELETE:

  • ๐ŸŸข DELETE {principalId}/appRoleAssignments/id
  • ๐ŸŸข DELETE {resourceId}/appRoleAssignedTo/id
  • ๐ŸŸก DELETE {principalId}/appRoleAssignedTo/id (Unsupported)
  • ๐ŸŸก DELETE {resourceId}/appRoleAssignments/id (Unsupported)

Examples

Using {principalId}/appRoleAssignments (1)

# POST {principalId}/appRoleAssignments (1)
$ az rest --method POST --url https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignments --body '{"principalId": "8aaa6158-7450-4407-ba08-61377f23d05f","resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4","appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"}'
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('8aaa6158-7450-4407-ba08-61377f23d05f')/appRoleAssignments/$entity",
  "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
  "createdDateTime": "2020-05-26T08:12:09.7096052Z",
  "deletedDateTime": null,
  "id": "WGGqilB0B0S6CGE3fyPQX_E14wqwY2lBhDlpweb26uo",
  "principalDisplayName": "myapp",
  "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
  "principalType": "ServicePrincipal",
  "resourceDisplayName": "Microsoft Graph",
  "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
}

# GET {principalId}/appRoleAssignments (1) to list
$ az rest --method GET --url https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignments
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('8aaa6158-7450-4407-ba08-61377f23d05f')/appRoleAssignments",
  "value": [
    {
      "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
      "createdDateTime": "2020-05-26T08:12:09.7096052Z",
      "deletedDateTime": null,
      "id": "WGGqilB0B0S6CGE3fyPQX_E14wqwY2lBhDlpweb26uo",
      "principalDisplayName": "myapp",
      "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
      "principalType": "ServicePrincipal",
      "resourceDisplayName": "Microsoft Graph",
      "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
    }
  ]
}

# GET {resourceId}/appRoleAssignments (3) to list doesn't return the desired assignment
$ az rest --method GET --url https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignments
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('a3efc889-f1b7-4532-9e01-91e32d1039f4')/appRoleAssignments",
  "value": []
}

# GET {resourceId}/appRoleAssignments/{id} (3) to show is unsupported, but works
$ az rest --method GET --url https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignments/WGGqilB0B0S6CGE3fyPQX_E14wqwY2lBhDlpweb26uo
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('a3efc889-f1b7-4532-9e01-91e32d1039f4')/appRoleAssignments/$entity",
  "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
  "createdDateTime": "2020-05-26T08:12:09.7096052Z",
  "deletedDateTime": null,
  "id": "WGGqilB0B0S6CGE3fyPQX_E14wqwY2lBhDlpweb26uo",
  "principalDisplayName": "myapp",
  "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
  "principalType": "ServicePrincipal",
  "resourceDisplayName": "Microsoft Graph",
  "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
}

# DELETE {principalId}/appRoleAssignments/{id} (1)
$ az rest --method DELETE --url https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignments/WGGqilB0B0S6CGE3fyPQX0ka7r14XhdLpH3TFDkPj6I

Using {resourceId}/appRoleAssignedTo (4)

# POST {resourceId}/appRoleAssignments (4) 
$ az rest --method POST --url https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignedTo --body '{"principalId": "8aaa6158-7450-4407-ba08-61377f23d05f","resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4","appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7"}'
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('a3efc889-f1b7-4532-9e01-91e32d1039f4')/appRoleAssignedTo/$entity",
  "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
  "createdDateTime": "2020-05-26T08:13:58.8505897Z",
  "deletedDateTime": null,
  "id": "WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y",
  "principalDisplayName": "myapp",
  "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
  "principalType": "ServicePrincipal",
  "resourceDisplayName": "Microsoft Graph",
  "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
}

# We don't list all appRoleAssignedTo items because the list is too large

# GET {resourceId}/appRoleAssignments/{id} (4) to show
$ az rest --method GET --url https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignedTo/WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('a3efc889-f1b7-4532-9e01-91e32d1039f4')/appRoleAssignedTo/$entity",
  "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
  "createdDateTime": "2020-05-26T08:13:58.8505897Z",
  "deletedDateTime": null,
  "id": "WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y",
  "principalDisplayName": "myapp",
  "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
  "principalType": "ServicePrincipal",
  "resourceDisplayName": "Microsoft Graph",
  "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
}

# GET {principalId}/appRoleAssignedTo/{id} (2) to show is unsupported, but works
az rest --method GET --url https://graph.microsoft.com/v1.0/servicePrincipals/8aaa6158-7450-4407-ba08-61377f23d05f/appRoleAssignedTo/WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('8aaa6158-7450-4407-ba08-61377f23d05f')/appRoleAssignedTo/$entity",
  "appRoleId": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
  "createdDateTime": "2020-05-26T08:13:58.8505897Z",
  "deletedDateTime": null,
  "id": "WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y",
  "principalDisplayName": "myapp",
  "principalId": "8aaa6158-7450-4407-ba08-61377f23d05f",
  "principalType": "ServicePrincipal",
  "resourceDisplayName": "Microsoft Graph",
  "resourceId": "a3efc889-f1b7-4532-9e01-91e32d1039f4"
}

# DELETE {resourceId}/appRoleAssignments/{id} (4) 
$ az rest --method DELETE --url https://graph.microsoft.com/v1.0/servicePrincipals/a3efc889-f1b7-4532-9e01-91e32d1039f4/appRoleAssignedTo/WGGqilB0B0S6CGE3fyPQX1Gp1oD6w_JCqCeCp4QUT0Y

However I still find the doc titles of the API appRoleAssignments and appRoleAssignedTo confusing. Will track at issue https://github.com/microsoftgraph/microsoft-graph-docs/issues/8412.

RE: https://github.com/Azure/azure-cli/issues/12137#issuecomment-596567479

โš  We will deprecate it in the future.

@jiasli any indication when az ad app permission admin-consent will be deprecated, and will there (please) be another CLI function to wrap the API calls you are documenting above?

Was this page helpful?
0 / 5 - 0 ratings