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
On a registration screen there is nothing fancy we are just give it a name and press create
After an app was created we want its client id and tenant id
Next navigate to client credentials to create new one
Make sure to copy secret value (not secret id) and keep it secret
Now it is time to give our app some permissions, navigate to API permissions section and add some
Choose Microsoft Graph
Add wanted permissions
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)
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