Azure Active Directory Graph API has been deprecated:
We strongly recommend that you use Microsoft Graph instead of Azure AD Graph API to access Azure Active Directory (Azure AD) resources. Our development efforts are now concentrated on Microsoft Graph and no further enhancements are planned for Azure AD Graph API. There are a very limited number of scenarios for which Azure AD Graph API might still be appropriate; for more information, see the Microsoft Graph or the Azure AD Graph blog post and Migrate Azure AD Graph apps to Microsoft Graph.
We need to switch Azure CLI to use Microsoft Graph API instead.
Microsoft Graph API Support
az restAs Microsoft Graph REST API v1.0 is now GA, we can call it directly with az rest to achieve the same effect as az ad commands, including all latest features from Microsoft Graph. It can automatically authenticate to Microsoft Graph.
redirectUris for an ApplicationOriginally posted at https://github.com/Azure/azure-cli/issues/9501#issuecomment-610979930. We call the Update application API. The GUID part in the following URLs are the object ID of the application.
# Get the application
az rest --method GET --uri 'https://graph.microsoft.com/v1.0/applications/b4e4d2ab-e2cb-45d5-a31a-98eb3f364001'
# Update `redirectUris` for `web` property
az rest --method PATCH --uri 'https://graph.microsoft.com/v1.0/applications/b4e4d2ab-e2cb-45d5-a31a-98eb3f364001' --body '{"web":{"redirectUris":["https://myapp.com"]}}'
Originally posted at https://github.com/Azure/azure-cli/issues/9250#issuecomment-603621148. We call the servicePrincipal: Add owner API.
appId=93dde3da-9fca-47dd-aee2-409b402ffed3
spObjectId=$(az ad sp show --id $appId --query objectId --output tsv)
# Get the object Id for the current user
ownerObjectId=$(az ad signed-in-user show --query objectId -o tsv)
# This applies to both user and service principal as owners
az rest -m POST -u https://graph.microsoft.com/beta/servicePrincipals/$spObjectId/owners/\$ref -b "{\"@odata.id\": \"https://graph.microsoft.com/beta/directoryObjects/$ownerObjectId\"}"
# To add a user as an owner
az rest -m POST -u https://graph.microsoft.com/beta/servicePrincipals/$spObjectId/owners/\$ref -b "{\"@odata.id\": \"https://graph.microsoft.com/beta/users/$ownerObjectId\"}"
# To add a service principal as an owner
az rest -m POST -u https://graph.microsoft.com/beta/servicePrincipals/$spObjectId/owners/\$ref -b "{\"@odata.id\": \"https://graph.microsoft.com/beta/servicePrincipals/$ownerObjectId\"}"
" in the JSON body (as contents) need to be escaped by \, even if the JSON body is surrounded by single quotes ': '{\"key\":\"value\"}'. For more info, please see Quoting IssuesA comment made by @jiasli in issue #9250 suggests that it should be possible to add a Service Principal as the Owner of an Application using the MS Graph API as follows.
az rest --method post --uri https://graph.microsoft.com/V1.0/applications/$appObjectId/owners/$ref --headers Content=Type=application/json --body "{\"@odata.id\":\"https://graph.microsoft.com/V1.0/directoryObjects/$spObjectId\"}"
this currently returns a Bad Request
{
"error": {
"code": "Request_BadRequest",
"message": "The reference target 'Application_xxx' of type 'Application' is invalid for the 'owners' reference.",
"innerError": {
"request-id": "bdf80192-becb-43d5-9447-e0e2a91d7d68",
"date": "2020-05-27T01:25:14"
}
}
}
xxx in the response above is the $spObjectId
Is this still a supported scenario using the MS Graph API?
az ad app owner add --id $appObjectId --owner-object-id $spObjectId
returns a similar result.
Hi @phil-holden, this error The reference target 'Application_xxx' of type 'Application' is invalid for the 'owners' reference. indicates xxx is an application object ID, not a service principal object ID.
If you are using Azure CLI, retrieve the service principal object ID with
# Use appId 46eb4122-bd2b-4f54-af7b-6d79b46ee31a
$ az ad sp show --id 46eb4122-bd2b-4f54-af7b-6d79b46ee31a --query objectId --output tsv
8aaa6158-7450-4407-ba08-61377f23d05f
If you are using Azure Portal, retrieve the service principal object ID with


Please notice that this issue is only for tracking purpose. For specific issues, you may create a new issue or reply to #9250.
Thanks @jiasli, works well when you have the right id!
@jiasli Thanks man! It solved our problem too! I was ripping my hair for 2 weeks now.
However just to understand.. Before I made the SP1 an owner of SP2, SP1 wasn't able to update the reply urls with error
az ad app update --id <appid> --add replyUrls "http://test" --debug
https://graph.windows.net:443 "GET /<tenantid>/applications?$filter=identifierUris%2Fany%28s%3As%20eq%20%<appid>%27%29&api-version=1.6 HTTP/1.1" 200 121
Insufficient privileges to complete the operation.
This was even despite SP1 having all the permissions on SP2 via full custom role, so I figured it's because it cannot read the Directory itself to list all applications and filter them.
Now when we made the SP1 owner of SP2, it can update the ReplyURLs without having any explicit API permissions nor any custom role as above.
What are we missing? Why does it work without Directory.Read.All?
Thank you!
Hi @Tbohunek,
Actually you are hitting a known issue that a Directory.Read.All permission should not be mandatory to update an application.
You can see from the code that a tenant query is mandatory before CLI can treat the --id as an object ID.
az โโโโ identifierUri โโโโ objectId This doesn't work because tenant-level query is needed to search for the app
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ This works because no tenant-level query is needed
This is certainly a design flaw and we plan to fix it in the Microsoft Graph integration. We are considering whether we should split identifierUris, appId and objectId to avoid confusions, but this would certainly be a BREAKING CHANGE.
For example, az role assignment create already has this fix. --assignee-object-id is used for bypassing Graph permission issues:
> az role assignment create -h
...
--assignee : Represent a user, group, or service principal. supported format:
object id, user sign-in name, or service principal name.
--assignee-object-id : Use this parameter instead of '--assignee' to bypass graph
permission issues. This parameter only works with object ids for
users, groups, service principals, and managed identities. For
managed identities use the principal id. For service principals, use
the object id and not the app id.
With that being said, I am not able to repro your scenario. I got Insufficient privileges to complete the operation. with both appId and appObjectId as expected:
# Owner Service Principal (not Application)
$ ownerObjectId="b0460b1a-d53a-42e8-a6a4-d9f43f0ccc73"
# Owned Application
$ appId="46eb4122-bd2b-4f54-af7b-6d79b46ee31a"
$ appObjectId="9580df2b-3c36-4a15-b909-15bb1b30b3a7"
# Add the owner
$ az rest --method post --uri https://graph.microsoft.com/V1.0/applications/$appObjectId/owners/\$ref --body "{\"@odata.id\":\"https://graph.microsoft.com/V1.0/directoryObjects/$ownerObjectId\"}"
$ az ad app update --id $appId --add replyUrls "http://test"
Insufficient privileges to complete the operation.
$ az ad app update --id $appObjectId --add replyUrls "http://test"
Insufficient privileges to complete the operation.
If an app can do tenant-level query without Directory.Read.All permission, this is definitely a security breach. ๐
Please check
az account showEven as an owner, the owner application still need Application.ReadWrite.OwnedBy permission to update its owned applications. For Microsoft Graph:

(Same for AD Graph, but due to the design flaw mentioned above, az ad app update won't work.)
To bypass the tenant-level query, you can directly use Microsoft Graph Update application to update the redirectUris.
$ az rest -m PATCH -u https://graph.microsoft.com/v1.0/applications/$appObjectId --body '{"web":{"redirectUris":["https://myapp.com"]}}' --verbose
Request URL: 'https://graph.microsoft.com/v1.0/applications/9580df2b-3c36-4a15-b909-15bb1b30b3a7'
Request method: 'PATCH'
Request headers:
'User-Agent': 'AZURECLI/2.6.0 (DEB)'
'Accept-Encoding': 'gzip, deflate'
'Accept': '*/*'
'Connection': 'keep-alive'
'x-ms-client-request-id': '65f37202-02b9-4723-b788-3dda87354bac'
'Content-Type': 'application/json'
'CommandName': 'rest'
'ParameterSetName': '-m -u --body --verbose'
'Authorization': 'Bearer eyJ0eXAiOiJKV...'
'Content-Length': '46'
Request body:
{"web":{"redirectUris":["https://myapp.com"]}}
Response status: 204
Response headers:
'Cache-Control': 'private'
'Content-Type': 'text/plain'
'request-id': '180fda8f-739e-41ea-b540-4ea8bfe8bed4'
'client-request-id': '180fda8f-739e-41ea-b540-4ea8bfe8bed4'
'x-ms-ags-diagnostic': '{"ServerInfo":{"DataCenter":"Southeast Asia","Slice":"SliceC","Ring":"5","ScaleUnit":"000","RoleInstance":"AGSFE_IN_24"}}'
'Strict-Transport-Security': 'max-age=31536000'
'Date': 'Thu, 28 May 2020 05:05:20 GMT'
Response content:
command ran in 1.641 seconds.
Again, if you have further questions regarding this specific topic, please kindly create a new issue. I am glad to help. ๐
@jiasli Thank you, this helped me find out that I was not using properly isolated CLI sessions, so adding the ReplyURL after making the SP and owner was run by wrong SP.
Now I granted the SP1 the Azure AD Graph: Application.ReadWrite.OwnedBy API permission and it works as expected.
Note that in the azure-cli 2.6.0 command it's visible that it uses the Azure AD Graph and not the new MS Graph:
```
az --version
azure-cli 2.6.0
command-modules-nspkg 2.0.3
core 2.6.0
nspkg 3.0.4
telemetry 1.0.4
az ad app update --id de9b193b-6b87-4ee8-954e-de1e6e6d99d9 --add replyUrls "http://test" --debug
https://graph.windows.net:443 "GET /
````
I am glad it is working for you.
Yes, az ad is still using AD Graph API. This's what this whole issue is talking about. Since Microsoft Graph Service Principal API is GA, we recommend using az rest instead of az ad for the time being until we fully migrate az ad to Microsoft Graph.
However, I tried to add Microsoft Graph: Application.ReadWrite.OwnedBy to the owner application, then I am able to list all applications in the tenant using this owner application!
> az rest -u 'https://graph.microsoft.com/v1.0/applications' --query "value[].displayName"
[
"sp19e92232ca5",
"sp168579505de",
"sp25f660911937e",
...
This is against the AAD doc https://docs.microsoft.com/en-us/graph/permissions-reference#remarks-3:
NOTE: Using the _Application.ReadWrite.OwnedBy_ permission to call
GET /applicationsto list applications will fail with a 403. Instead useGET servicePrincipals/{id}/ownedObjectsto list the applications owned by the calling application.
@jiasli thanks for all the examples of utilizing az rest to call service principals. I'm fighting with a possible edge case now. In an attempt to categorize and programmatically rotate client secrets we're aiming to use Tags on service principal objects. For the most part, this works fine by running az commands like:
az ad sp update --id appID --add tags "tags:value"
Before I got to this point I ran into the nanosecond/microsecond Azure Portal bug with az cli and I am getting similar behavior when trying to update tags on a service principal using the above command. The client secrets were set using the "old" method, outlined below:
$servicePrincipal = New-AzureRmADServicePrincipal -ApplicationId $spn.ApplicationId
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($servicePrincipal.Secret)
$spnPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$SecureStringPassword = ConvertTo-SecureString -String $spnPassword -AsPlainText -Force
#Apply the password to the app
New-AzureRmADAppCredential -ApplicationId $spn.ApplicationId -Password $SecureStringPassword
When trying to tag service principals created this way I am finding the generic error: az : Update to existing credential with KeyId 'keyID' is not allowed.
Do you have any working examples of updating tags with the az rest method? I can't seem to get the payload correct.
az rest -m "PATCH" -u https://graph.microsoft.com/v1.0/servicePrincipals/objID --headers "Content-Type=application/json" -b '{\"tags\":\""tag:value"\"}'
Any help is appreciated, thanks!
@scals44, do you mind creating a new issue for us to track? Thank you.
sorry to jump on this thread but is there any easy of updating the redirectUris (by adding one property) using the rest API...it seems like patch will overwrite all of the redirectUrls e.g:
az rest --method PATCH --uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" --headers 'Content-Type=application/json' --body "{\"spa\":{\"redirectUris\": [\"https://addanotherurlhere.com\"]}}"
Ends up deleting all the other URLs, since the cli doesn't support updating spa URLs at all yet but it does support adding only one url was wondering if this could be done via the rest api.
@jaslloyd Someone else may correct me, but I don't believe there is a way to atomically add one URI. However, you can query the Graph API to obtain the existing redirect URIs and use that to formulate your PATCH body:
az rest --method GET --uri 'https://graph.microsoft.com/v1.0/applications/OBJECT-ID-HERE?$select=spa' --headers 'Content-Type=application/json' --query "spa.redirectUris"
returns:
[
"https://myapp.com"
]
With that said, I wish that the CLI itself were more capable when it comes to many common functions ๐
Hi @jaslloyd, @rmsy's answer is correct. As spa.redirectUris is a property, there is no API from MS Graph API to add new entries to it. You need to GET the value first, do the addition, then PATCH it back.
@jaslloyd, here is my build step for ensuring an URI is included:
SPA_OLD_URIS=$(az rest --method GET --uri "https://graph.microsoft.com/v1.0/applications/$APP_ID" | jq -c '.spa.redirectUris | unique')
SPA_NEW_URIS=$(printf '%s\n' "$SPA_OLD_URIS" | jq -c ". + [\"$NEW_URI\"] | unique")
if [ "x$SPA_NEW_URIS" != "x$SPA_OLD_URIS" ]; then
SPA_PATCH=$(printf '%s\n' "$SPA_NEW_URIS" | jq -c '{"spa":{"redirectUris":.}}')
az rest --method PATCH --uri "https://graph.microsoft.com/v1.0/applications/$APP_ID" --headers 'Content-Type=application/json' --body="$SPA_PATCH"
fi
(I actually need two URIs for each environment, so I have jq -c ". + [\"$NEW_URI\", \"$NEW_URI/silent-renew.html\"] | unique" for the addition).
It could probably be simplified using the built-in JMESPath capability of az to directly calculate the patch in the initial GET, but this was easier to debug.
Note that the | uniques are important to avoid writing the list when the URI was already there, and I didn't see how to easily discard duplicates in JMESPath.
@rmsy @jialindai @jan-hudec thanks for the help, p.s by chance do you happen to know what permissions a service principal needs to update the redirect_uris of itself? I tried adding Application.ReadWrite.OwnedBy but no luck
Is there an indication when azure cli will be migrated to Microsoft Graph API?
Time flies! Still nothing on Microsoft side despite them convincing us to migrate our workloads...

Azure CLI team is currently working on the ADAL -> MSAL migration. We will start the planning and implementation of Active Directory Graph -> Microsoft Graph migration once the previously task is done. + @achandmsft
Meanwhile, Microsoft Graph team is currently developing their own CLI tool: https://github.com/microsoftgraph/msgraph-cli. This project is still at its early phase. You are very welcome to play with it and share any feedback.
@jiasli thanks for the clarification. We are all looking forward. โค๏ธ
However, isn't it a bit contra-productive to introduce yet another CLI tool on top of az? The confusion between Get-AzADxxx and Get-AzureADxxx (think Connect-AzureAD, Login-AzAccount, Connect-AzAccount) is already enough. ๐ค
We are currently are running into the same issue. Adopting other commands or tools is contra-productive indeed. Please give us a clear timeline.
Most helpful comment
@jaslloyd Someone else may correct me, but I don't believe there is a way to atomically add one URI. However, you can query the Graph API to obtain the existing redirect URIs and use that to formulate your PATCH body:
returns:
With that said, I wish that the CLI itself were more capable when it comes to many common functions ๐