I would like to give admin consent to API permissions through powershell script - azure

I have a code in Az module of powershell to create appID, app secret and assign API permission. How do I grant admin consent to all the API permissions that I assigned to the AzApp?
...
$context = Get-AzContext
$ResourceAppIdURI = "https://graph.windows.net/"
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceAppIdURI).AccessToken
$headers = #{ }
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$objectID = $myApp.ObjectId
$url = "https://graph.windows.net/$tenant/applications/{0}?api-version=1.6" -f $objectID
Write-Host "URL: " $url
$postData = "{`"requiredResourceAccess`":[
{`"resourceAppId`":`"00000003-0000-0000-c000-000000000000`",
`"resourceAccess`":[
{`"id`":`"e1fe6dd8-ba31-4d61-89e7-88639da4683d`",`"type`":`"Scope`"},
{`"id`":`"7ab1d382-f21e-4acd-a863-ba3e13f7da61`",`"type`":`"Role`"},
{`"id`":`"5b567255-7703-4780-807c-7be8301ae99b`",`"type`":`"Role`"},
{`"id`":`"e2a3a72e-5f79-4c64-b1b1-878b674786c9`",`"type`":`"Role`"},
{`"id`":`"df021288-bdef-4463-88db-98f22de89214`",`"type`":`"Role`"}
]
}]
}";
Invoke-RestMethod -Uri $url -Method "PATCH" -Headers $headers -Body $postData
Write-Host "App created..."
Write-Host "AppID: " $myApp.ApplicationId
Write-Host "App Secret: " $secret
Write-Host "TenantID: " $tenant.Id

There is no API exposed by Microsoft to grant admin consent for Azure AD application / service principal. You can vote this post on User Voice.
There is a workaround:
Call Microsoft Graph API Create a delegated permission grant and Grant an appRoleAssignment to a service principal in Powershell.
A sample for your reference:
$context = Get-AzContext
$ResourceAppIdURI = "https://graph.windows.net/"
$ResourceGraphURI = "https://graph.microsoft.com/"
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceAppIdURI).AccessToken
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceGraphURI).AccessToken
$clientID = "d154cc56-f1a2-4906-9f26-bfb4756f9c20"
$resourceID = "08a1faff-51c1-4cbb-81c4-1bc11286da76"
$scopes = "Sites.Read.All User.Read User.Read.All User.ReadBasic.All"
$body = #{
clientId = $clientID
consentType = "AllPrincipals"
principalId = $null
resourceId = $resourceID
scope = $scopes
startTime = "2019-10-19T10:37:00Z"
expiryTime = "2020-10-19T10:37:00Z"
}
$apiUrl = "https://graph.microsoft.com/beta/oauth2PermissionGrants"
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization = "Bearer $($graphToken)" } -Method POST -Body $($body | convertto-json) -ContentType "application/json"
$principalId = "d154cc56-f1a2-4906-9f26-bfb4756f9c20"
$body1 = #{
principalId = $principalId
resourceId = $resourceID
appRoleId = "df021288-bdef-4463-88db-98f22de89214"
}
$apiUrl1 = "https://graph.microsoft.com/beta/servicePrincipals/$($principalId)/appRoleAssignedTo"
Invoke-RestMethod -Uri $apiUrl1 -Headers #{Authorization = "Bearer $($graphToken)" } -Method POST -Body $($body1 | convertto-json) -ContentType "application/json"
For the first call https://graph.microsoft.com/beta/oauth2PermissionGrants:
clientID is the object id of the service principal (not Azure AD application), you can find it using Get-AzADServicePrincipal. You can also find it on Azure Portal - Azure Active Directory - Enterprise Applications, search for the name of your Azure AD application.
resouceID is the object id of Microsoft Graph service principal. You can find under Enterprise applications (search for "00000003-0000-0000-c000-000000000000").
scopes are the delegated permissions you want to grant admin consent.
For the second call https://graph.microsoft.com/beta/servicePrincipals/$($principalId)/appRoleAssignedTo:
principalId is the same as clientID mentioned above.
appRoleId is the application permission id.

Actually, the Azure AD PowerShell module provides a cmdlet equivalent for Application Permissions : New-AzureADServiceAppRoleAssignment.
Even if it's poorly documented, the command adds the requested application permissions (and grant admin consent if you have the right to do so) to your AAD Application (through the service principal).
# If it's not the case, declare your AAD Application as a service principal (Enterprise Application)
$aadappsp = New-AzureADServicePrincipal -AppId "AAD_APPLICATION_ID"
# Id of the application permission (role)
$roleId = "2a8d57a5-4090-4a41-bf1c-3c621d2ccad3" # TermStore.Read.All
# Object Id of the concerned Service Principal (could be Graph or SharePoint for example)
# (Not the Application Id like "00000003-0000-0ff1-ce00-000000000000" for SharePoint)
$aadSpObjectId = "c30e8a24-ff90-464e-aed3-7c39a7bdc280"
# Register the application permission
New-AzureADServiceAppRoleAssignment -ObjectId $aadappsp.ObjectId -Id $roleId -PrincipalId $aadappsp.ObjectId -ResourceId $aadSpObjectId
It's using a dedicated endpoint, so don't be surprised if you have this display once the command correctly executed:
(permissions added through PowerShell appear as "Other permissions granted for...")
To avoid that, you have to first add them through interface or with New-AzureADApplication (to register the permissions as "configured") and New-AzureADServicePrincipal (to grant admin consent properly for your organization).
Sadly, there's no cmdlet for granting admin consent on Delegated Permissions, so the answer provided by #Allen Wu still works in this case (just update the URIs to use v1.0 version instead of beta).

Related

Using token from Connect-AZAccount on Intune Graph calls

So, after years consuming valuable information from StackOverflow this is my first question!
I used part of a code I found when researching as I start to move away from App Secrets in Graph API and prepare to use Managed Identities.
It successfully authenticate and generate the token when running locally(haven't tested anything on Azure Automation yet as this would be the PoC) but it will not work for everything in Graph, it works when querying AD Users and Groups but not on anything else, it seems it is not getting all the scopes the accounts has access to although the account is global admin.
This part will check if you are using Managed Identity or prompt for login
#Requires -Modules #{ ModuleName="Az.Accounts"; ModuleVersion="2.7.0" } , #{ ModuleName="Az.Resources"; ModuleVersion="5.1.0" }
Param(
[Switch]$nonInteractive
)
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
$res = [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
if(!$Global:AZConnection){
try{
if($nonInteractive){
Write-Output "Logging in with MI"
$Null = Connect-AzAccount -Identity -ErrorAction Stop
Write-Output "Logged in as MI"
}else{
Write-Output "Logging in to Azure AD"
$Global:AZConnection = Connect-AzAccount -Force -ErrorAction Stop
Write-Output "Logged in to Azure AD with $($Global:AZConnection.Context.Account.id)"
}
}catch{
Throw $_
}
}
After this it will get the context and authenticate against graph.microsoft.com & graph.windows.net to get both tokens.
It will also prepare the headers to be used on WebRequests
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graph = ([Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Always, $null, "https://graph.microsoft.com"))
$graphToken = $graph.AccessToken
$AAD = ([Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net"))
$aadToken = $AAD.AccessToken
$HeadersGraph = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $graphToken"
}
$HeadersAAD = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $aadToken"
}
Trying to use the token to query user or group information will work
$UserData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Users" -Method GET -Headers $HeadersGraph
$GroupData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Users" -Method GET -Headers $HeadersGraph
But if I try to query any Intune or SharePoint URI it will give me:
Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
$deviceData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices" -Method GET -Headers $HeadersGraph
$SharepointData = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/sites" -Method GET -Headers $HeadersGraph
I can use these Tokens to authenticate on other modules but I was trying to avoid it and keep everything on Graph web requests
#This also works
$Intune = Connect-MgGraph -AccessToken $graphToken
$AzureAD = Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id
Does anyone know of any resource principal that I can authenticate that would give me a valid token to make calls to SharePoint and Intune?
I assume that you're aware that this is a permission issue and not a token issue.
I have no solution for your problem. I just want to let you know, that nowadays there is an easier way to get Graph Access tokens (since you are already using the Az.Accounts module).
https://learn.microsoft.com/en-us/powershell/module/az.accounts/get-azaccesstoken

Access Azure PIM api in azure pipelines via service principal

I'm trying to call the azure privileged identity management api (https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess) in an azure pipeline. I have the following code to call the register method, but it is not working, but I can't figure out what is wrong. Let me show the code first:
install-module azureadpreview -Force
import-module azureadpreview
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
$pimToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "<what do i enter here?>").AccessToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -MsAccessToken $graphToken
Write-Output "Create PIM role"
$Group = New-AzureADMSGroup -DisplayName "TestPIMGroup" -Description "TestForPim" -MailEnabled $false -SecurityEnabled $true -MailNickName "NOTUSED" -IsAssignableToRole $true
Write-Output "Test api call"
$Headers = #{
"Accept" = "*/*"
"Accept-Language" = "en"
"Authorization" = "Bearer {0}" -f $pimToken
"Content-Type" = "application/json"
}
$Body = #{
externalId = $Group.Id
} | ConvertTo-Json
$URL = 'https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess/aadGroups/resources/register'
Write-Output "Body: $Body"
$HeaderJson = $Headers | ConvertTo-Json
Write-Output "Headers: $HeaderJson"
try {
$QueryResponse = Invoke-RestMethod -Uri $URL -Headers $Headers -Method POST -Body $Body
}
catch {
$_.Exception.Response
$result = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($result)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
$responseBody
exit 1
}
$QueryResponse.value
So what I'm trying to accomplish, it to create a PIM group, and enable privileged access on it. The azureadpreview module has functionality to create a PIM group, so I use that. This works perfectly. The method of getting the token for the service principal I got from this post.
Now to enable privileged access on it, I need to directly call the API, because there doesn't seem to be any powershell command for it. This where things get tricky. The API call returns a 500 internal server error, with the only error in the body being an error has occurred. So that doesn't really tell me anything. So I started investigating:
When I don't pass a token, I get an unauthorized exception
When I pass nonsense as a token, I get an internal server error. So this pointed me in the direction that something is wrong with the token. I think there is something wrong with the audience tag in the token.
I tried all kinds of URL's in the token, which all show me a 500 as response.
I recorded the api call the browser did in azure portal when doing the same call. I read the token from this request, and it got me some guid as aud. When I use this guid, it given me a Unauthorized response
Can anyone help me with my situation?
answer restriction:
I need to be able to access the pim api by reusing the service principal I'm already using in my azure pipeline via my service connection. So I'm not looking for answers about creating certificates in azure an authenticating using it.
Edit:
Some background info:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/42203638-allow-privileged-access-for-groups-to-be-enabled-f
https://feedback.azure.com/forums/34192--general-feedback/suggestions/42644962-possibility-for-azure-ad-privileged-access-groups
https://github.com/MicrosoftDocs/azure-docs/issues/70296#issuecomment-776069283
The guid you get from token in browser in azure portal should be correct.
But this API endpoint looks like to be not exposed for external usage.
We can not generate an application token for this resource currently.
As you see there are several user voice posts are requiring this feature.
Keep voting up the posts will be helpful.

Create azure application through Az module and assign API permissions using powershell

I have written a script which creates azure application using Az module, creates secret key, assigns owner. But assigning API permission gives insufficient permission error. The user is an admin user. Still unable to assign API permission. What wrong am I doing?
$ErrorActionPreference = 'Stop'
Connect-AzAccount
Import-Module Az.Resources
$tenant = Get-AzTenant
Set-AzContext -TenantId $tenant.Id
$AppName = Read-Host -Prompt 'Enter Application name '
$myApp = New-AzADApplication -DisplayName $AppName -IdentifierUris "http://$AppName.com"
Write-Host "App registered.."
$sp = New-AzADServicePrincipal -ApplicationId $myApp.ApplicationId -Role Owner
Write-Host "Service principal registered.."
$startDate = Get-Date
$endDate = $startDate.AddYears(100)
$secret = Read-Host -Prompt 'Enter App Secret Key ' -AsSecureString
$secPassword = ConvertTo-SecureString -AsPlainText -Force -String $secret
New-AzADAppCredential -ObjectId $myApp.ObjectId -StartDate $startDate -EndDate $endDate -Password $secPassword
$ResourceAppIdURI = "https://graph.windows.net/"
# $authority = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token"
$authority = "https://login.windows.net/$tenant/oauth2/token"
$ClientCred = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" -ArgumentList $myApp.ApplicationId, $secret
$AuthContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority,$false
$AuthContext.TokenCache.Clear()
Start-Sleep -Seconds 10
$Token = $Authcontext.AcquireTokenAsync($ResourceAppIdURI, $ClientCred)
$AuthHeader = #{"Authorization" = $Token.Result.CreateAuthorizationHeader();"Content-Type"="application/json"}
$url = "https://graph.windows.net/$tenant/applications/$($myApp.ObjectID)?api-version=1.6"
Write-Host "URL: " $url
$postData = "{`"requiredResourceAccess`":[{`"resourceAppId`":`"00000003-0000-0000-c000-000000000000`",
`"resourceAccess`":[{`"id`":`"e1fe6dd8-ba31-4d61-89e7-88639da4683d`",`"type`":`"Scope`"}]}]}";
$result = Invoke-RestMethod -Uri $url -Method "PATCH" -Headers $AuthHeader -Body $postData
Write-Host "Result of App API permission: " $result
In my case, the easiest way to do this without messing around with http requests, was to combine the Azure-powershell module and the Az cli module
So, once I have created my new app:
$myApp = New-AzADApplication -DisplayName $AppName -IdentifierUris "http://$AppName.com"
Then I would login into azure using the Az Cli, and, for instance:
Add some api permissions
Grant these permissions directory admin consent ( if needed )
. { $azcliLogin = az login }
. { az account set --subscription $config.subscriptionId }
. { az ad app permission add --id $myApp.appid --api 00000002-0000-0000-c000-000000000000 --api-permissions 78c8a3c8-a07e-4b9e-af1b-b5ccab50a175=Role }
. { $appApiGrant = az ad app permission grant --id $config.azureAccess.appid --api 00000002-0000-0000-c000-000000000000 }
. { az ad app permission admin-consent --id $myApp.appid }
Where:
--api 00000002-0000-0000-c000-000000000000 Refers to Microsoft Graph API
--api-permissions 78c8a3c8-a07e-4b9e-af1b-b5ccab50a175=Role Refers to some role on this api, as Directory.ReadWrite.All
You can get the required API and API-PERMISSIONS guids from the App manifiest in Azure
This way you create the app with the required granted api permissions, in a single powershell script.
If you want to call Azure AAD graph API to assign permissions with OAuth 2.0 client credentials flow, we need to provide enough permissions(Azure AD Graph -> Aapplication permissions -> Application.ReadWrite.All)
Besides, regarding how to assign permissions to AD application with PowerShell, we also can use PowerShell module AzureAD.
For example
Connect-AzureAD
$AppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]#{
ResourceAppId = "00000003-0000-0000-c000-000000000000";
ResourceAccess =
[Microsoft.Open.AzureAD.Model.ResourceAccess]#{
Id = "";
Type = ""},
[Microsoft.Open.AzureAD.Model.ResourceAccess]#{
Id = "";
Type = ""}
}
Set-AzureADApplication -ObjectId <the app object id> -RequiredResourceAccess $AppAccess
Update
According to my test, when we use Az module, we can use the following method to get access token and call AAD graph rest API. But please note that when you use the method, the account you use to run Connect-AzAccount should be Azure AD Global Admin
Connect-AzAccount
$context =Get-AzContext
$dexResourceUrl='https://graph.windows.net/'
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account,
$context.Environment,
$context.Tenant.Id.ToString(),
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,
$null, $dexResourceUrl).AccessToken
# assign permissions
$headers =#{}
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$body = "{
`n `"requiredResourceAccess`": [{
`n `"resourceAppId`": `"00000003-0000-0000-c000-000000000000`",
`n `"resourceAccess`": [
`n {
`n `"id`": `"405a51b5-8d8d-430b-9842-8be4b0e9f324`",
`n `"type`": `"Role`"
`n },
`n {
`n `"id`": `"09850681-111b-4a89-9bed-3f2cae46d706`",
`n `"type`": `"Role`"
`n }
`n ]
`n }
`n ]
`n}
`n"
$url ='https://graph.windows.net/hanxia.onmicrosoft.com/applications/d4975420-841f-47d5-a3d2-0870901f13cd?api-version=1.6'
Invoke-RestMethod $url -Method 'PATCH' -Headers $headers -Body $body
#check if adding the permissions you need
$headers =#{}
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$url ='https://graph.windows.net/hanxia.onmicrosoft.com/applications/<aad application object id>?api-version=1.6'
$response=Invoke-RestMethod $url -Method 'GET' -Headers $headers
$response.requiredResourceAccess | ConvertTo-Json

How to get the access token to Azure API Management programmatically?

I'm trying to implement Azure Active Directory in my API Management instance using the Protect an API by using OAuth 2.0 with Azure Active Directory and API Management doc. The doc suggests that in order to get the access token I need to use the Developer Portal.
My problem is: An external application is going to communicate with API Management. Is there a way to omit the Developer Portal and get the access token programmatically?
It's a pain but thanks to Jos Lieben I am able to do it with this Powershell function
It's specifically for granting API access on behalf of the Org, but as you can see you can extract the commands to get and use the API token.
Original Author Link: https://www.lieben.nu/liebensraum/2018/04/how-to-grant-oauth2-permissions-to-an-azure-ad-application-using-powershell-unattended-silently/
Function Grant-OAuth2PermissionsToApp{
Param(
[Parameter(Mandatory=$true)]$Username, #global administrator username
[Parameter(Mandatory=$true)]$Password, #global administrator password
[Parameter(Mandatory=$true)]$azureAppId #application ID of the azure application you wish to admin-consent to
)
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
$res = login-azurermaccount -Credential $mycreds
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$refreshToken = #($context.TokenCache.ReadItems() | where {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
$body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
$apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
$header = #{
'Authorization' = 'Bearer ' + $apiToken.access_token
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()
}
$script:url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$azureAppId/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $header -Method POST -ErrorAction Stop
}

Programatically Add users to group in AzureAD as group owner

Is there an API for AzureAD which allows group owners to add users to their owned groups?
Using the Graph api's looks to require admin consent (granting admin permissions) and so provides access to more than the user's owned groups. I don't want to grant this.
I also don't want to use delegated access - an admin shouldn't need to be present for a group owner (perhaps a service principal?) to add users to their own group?
I have some interesting findings.
A) If you set a service principal as the owner of a group, and want to manage the group with the service principal, you have to add and grant necessary permission for Azure AD Graph API.
B) If you set a user as the owner of a group, then you can use the public client (1b730954-1685-4b74-9bfd-dac224a7b894) and user credential to acquire token, and then call the AAD graph API as the user to manage the group.
Here, I use PowerShell to make http requests. You can use other program languages.
# Get token for Azure AD Graph
$uri = "https://login.microsoftonline.com/{tenant_name_or_id, for example: hanxia.onmicrosoft.com}/oauth2/token"
$body = #{grant_type='password';resource='https://graph.windows.net';client_id='1b730954-1685-4b74-9bfd-dac224a7b894';username='normaluser#hanxia.onmicrosoft.com';password='a*******7'}
$result = Invoke-RestMethod -Method Post -Uri $uri -Body $body
$accessToken = $result.access_token
# Azure AD Graph. Get group information
$tenantId = "e4c9ab4e-bd27-40d5-8459-230ba2a757fb"
$groupId = "f37d06f2-e26f-45f9-b9b1-da13d0b79ea7"
$apiVersion = "1.6"
$result = Invoke-WebRequest -Method Get `
-Uri ("https://graph.windows.net/" + $tenantId + "/groups/" + $groupId +"?api-version=" + $apiVersion) `
-Headers #{ "Authorization" = "Bearer " + $accessToken }
$result.Content | ConvertFrom-Json | ConvertTo-Json
# Azure AD Graph. Get users in group
$result = Invoke-WebRequest -Method Get `
-Uri ("https://graph.windows.net/" + $tenantId + "/groups/" + $groupId +"/`$links/members" +"?api-version=" + $apiVersion) `
-Headers #{ "Authorization" = "Bearer " + $accessToken }
$result.Content | ConvertFrom-Json | ConvertTo-Json
# Azure AD Graph. Add user to group
$userObject = #{"url" = "https://graph.windows.net/e4c9ab4e-bd27-40d5-8459-230ba2a757fb/directoryObjects/3f43b292-adac-48f9-a623-ee76ca9c7174"} | ConvertTo-Json
$result = Invoke-WebRequest -Method Post `
-Uri ("https://graph.windows.net/" + $tenantId + "/groups/" + $groupId +"/`$links/members" +"?api-version=" + $apiVersion) `
-Headers #{ "Authorization" = "Bearer " + $accessToken; "Content-Type" = "application/json" } `
-Body $userObject
if($result.StatusCode -eq 204){ Write-Host "User added" }
Note:
1b730954-1685-4b74-9bfd-dac224a7b894 is a common application from Microsoft for every tenant.
API Reference: Operations on groups | AAD Graph API reference
The reason that consent is needed is that while the group owner has rights to add users to a group, an app by default does not. The delegated permissions give the app rights to modify groups on behalf of the user, depending on the user's rights as well. It's usually the better approach. App permissions give the app itself permissions to act without a user. Which is often too much, but has its use cases.
You need to grant the delegated permission at least to the app so it can do the modification on behalf of the user.

Resources