Talking to Microsoft Graph API or how to retrieve AAD users

I want to get list of AD users so I can make an desicion for 3rd party app to remove deactivated users

Azure App Registration and Permissions

To do so we gonna need register our app which can be done from ActiveDirectory app registrations section

register new app

On a registration screen there is nothing fancy we are just give it a name and press create

registration

After an app was created we want its client id and tenant id

identifiers

Next navigate to client credentials to create new one

where to create azure app client credentials

where to create azure app client credentials

where to create azure app client credentials

Make sure to copy secret value (not secret id) and keep it secret

where to create azure app client credentials

Now it is time to give our app some permissions, navigate to API permissions section and add some

adding permissions to azure app

Choose Microsoft Graph

adding permissions to azure app

Add wanted permissions

adding permissions to azure app

Note that we are choosing application level permissions so they will work in background

Now we need to grant requested permissions (even so we have add them manually it is still requires contest)

adding permissions to azure app

It will just show an confirmation dialogue where we should press yes and if everything fine we should see green checkmars aside permissions

Microsoft Graph API

If previous septs were done correctly we should have:

tenant_id=xxx
client_id=xxx
client_secret=xxx

# https://learn.microsoft.com/en-us/graph/auth-v2-service?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#token-request
token=$(curl -s -X POST "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=$client_id&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=$client_secret&grant_type=client_credentials" | jq -r ".access_token")

# https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http
curl -s https://graph.microsoft.com/v1.0/users -H "Authorization: Bearer $token" | jq ".value"

So with this approach we may talk to Azure ActiveDirectory in our background jobs

AZ CLI Azure App

There is a way to automate application creation

BUT I was not able to setup permissions


# STEP 0: AZ CLI
echo current
az account show --query name --output tsv
echo available
az account list --query "[].{name:name}" --output tsv
echo change
az account set --subscription="mysubs"

tenant_id=$(az account show --query tenantId --output tsv)
echo "tenant_id: $tenant_id"

# STEP 1: APP REGISTRATION

# https://learn.microsoft.com/en-us/cli/azure/ad/app?view=azure-cli-latest
az ad app create --display-name mactemp

# https://learn.microsoft.com/en-us/cli/azure/ad/app?view=azure-cli-latest#az-ad-app-list
client_id=$(az ad app list --query "[?displayName=='mactemp'].{client_id:appId}[0]" --output tsv)
echo "client_id: $client_id"

# https://learn.microsoft.com/en-us/cli/azure/ad/app/credential?view=azure-cli-latest#az-ad-app-credential-reset
client_secret=$(az ad app credential reset --id $client_id --append --display-name mactemp --years 10 --query password --output tsv)
echo "client_secret: $client_secret"



# # https://learn.microsoft.com/en-us/cli/azure/ad/app/permission?view=azure-cli-latest#az-ad-app-permission-admin-consent
# az ad app permission admin-consent --id $client_id

# # https://learn.microsoft.com/en-us/cli/azure/ad/app/permission?view=azure-cli-latest#az-ad-app-permission-grant
# az ad app permission grant --id $client_id --api 00000003-0000-0000-c000-000000000000

# # az ad sp show --id 00000003-0000-0000-c000-000000000000
# # az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[*].{id:id,name:value}" --output tsv
# id=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?value=='Directory.Read.All'].id" --output tsv)
# az ad app permission add --id $client_id --api 00000003-0000-0000-c000-000000000000 --api-permissions $id=Role
# id=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?value=='User.Read.All'].id" --output tsv)
# az ad app permission add --id $client_id --api 00000003-0000-0000-c000-000000000000 --api-permissions $id=Role

# az ad sp create --id $client_id
# az ad app permission grant --id $client_id --api 00000003-0000-0000-c000-000000000000 --scope Directory.Read.All
# az ad app permission grant --id $client_id --api 00000003-0000-0000-c000-000000000000 --scope User.Read.All


# STEP 2: TOKEN

# https://learn.microsoft.com/en-us/graph/auth-v2-service?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#token-request
token=$(curl -X POST "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=$client_id&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=$client_secret&grant_type=client_credentials" | jq -r ".access_token")

# STEP 3: API
curl https://graph.microsoft.com/v1.0/users -H "Authorization: Bearer $token" | jq

# STEP 4: CLEANUP
az ad app delete --id $client_id

Creating and Azure app for device flow

Trying to mimicate Azure Device Flow Authentication in Go which has quite a good example of how to get up and running with device auth flow in Go.

The same way as previous we need to make sure we are in the right subscription

# current
az account show --query name --output tsv
# available
az account list --query "[].{name:name}" --output tsv
# change
az account set --subscription="mysubs"

# tenant_id
tenant_id=$(az account show --query tenantId --output tsv)
echo "tenant_id: $tenant_id"

Now we can create our app

# https://learn.microsoft.com/en-us/cli/azure/ad/app?view=azure-cli-latest
az ad app create --display-name mactemp

# https://learn.microsoft.com/en-us/cli/azure/ad/app?view=azure-cli-latest#az-ad-app-list
client_id=$(az ad app list --query "[?displayName=='mactemp'].{client_id:appId}[0]" --output tsv)
echo "client_id: $client_id"

# https://learn.microsoft.com/en-us/cli/azure/ad/app/credential?view=azure-cli-latest#az-ad-app-credential-reset
client_secret=$(az ad app credential reset --id $client_id --append --display-name mactemp --years 10 --query password --output tsv)
echo "client_secret: $client_secret"

# allow public flows
# https://learn.microsoft.com/en-us/azure/healthcare-apis/register-application-cli-rest#change-the-flag-for-public-client-applications
# https://github.com/Azure/azure-cli/issues/7955#issuecomment-611976458
# az ad app show --id $client_id | grep ublic -C 1
# this should enable "Allow public client flows" checkbox on "Authentication" tab in the sidebar
az ad app update --id $client_id --set isFallbackPublicClient=true

The device authentication flow is described here

curl -X POST "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/devicecode" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=$client_id
&scope=user.read%20openid%20profile"

The response will be something like:

{
  "user_code":"CW3AD73A4",
  "device_code":"CAQABAAEAAAD--DLA3....",
  "verification_uri":"https://microsoft.com/devicelogin",
  "expires_in":900,
  "interval":5,
  "message":"To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CW3AD73A4 to authenticate."
}

While user opens browsers and logs in, your app is expected to be pulling results

curl -X POST "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=$client_id&device_code=CAQABAAEAAAD--DLA3VO....."

And the response will be something like:

{
  "token_type":"Bearer",
  "scope":"openid profile User.Read email",
  "expires_in":4184,
  "ext_expires_in":4184,
  "access_token":"eyJ0eXAiOiJKV1QiLC...",
  "id_token":"eyJ0eXAiOiJKV1QiLCJhbG..."
}

Libraries in majority of languages will have something around validation of tokens but we will try to call user info endpoint found at https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

curl https://graph.microsoft.com/oidc/userinfo -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25j..."

The response will be something like:

{
  "sub":"g_461q6Bo2lIySX1faMNhrOkMY_Q2h6i-1GpzjinOqM",
  "name":"Alexandr Marchenko",
  "family_name":"Marchenko",
  "given_name":"Alexandr",
  "picture":"https://graph.microsoft.com/v1.0/me/photo/$value",
  "email":"[email protected]"
}

Noth that access_token is used, if you will try to use id_token you will get an error like:

{
  "error":{
    "code":"InvalidAuthenticationToken",
    "message":"Access token validation failure. Invalid audience.",
    "innerError":{
      "date":"2023-02-04T14:50:48",
      "request-id":"b9a5ab76-0620-4f44-9987-9970e7438d2f",
      "client-request-id":"b9a5ab76-0620-4f44-9987-9970e7438d2f"
    }
  }
}

The Go lang implementation will be something like:

package main

import (
	"fmt"
	"os"

	"github.com/Azure/go-autorest/autorest/azure/auth"
)

func main() {
	clientID := os.Args[1]
	tenantID := os.Args[2]
	deviceConfig := auth.NewDeviceFlowConfig(clientID, tenantID)

	// https://github.com/Azure/azure-sdk-for-go/issues/3804#issuecomment-452118022
	spToken, _ := deviceConfig.ServicePrincipalToken()
	token := spToken.Token()
	fmt.Println("AccessToken")
	fmt.Println(token.AccessToken)
}

And to run it:

go run device/main.go $client_id $tenant_id

But unfortunately at moment that does not work and complains about wrong requested scopes, so probably need to be done semi-manually