Welcome to 3rd post of the series Learn How to Use Microsoft Graph API with Joy. Today in this blog post, I will try to show how easily you can get started with PowerShell to make MS Graph API calls.
The previous posts of this series is listed below for your convenience.
- Post #1 – Explore Graph with Graph Explorer – Getting started with MS Graph API
- Post #2 – Understanding AUTH for MS Graph API
In the previous posts of this series, we talked about how AUTH plays an important role, discussing the different permissions types and deciding the most suitable MSAL auth flow method to use to work with Microsoft Graph.
Today’s blog post basically serves the purpose of an implementation guide to whatever we have discussed in the previous post, with the context of PowerShell as the client app to make API calls to Microsoft Graph.
But before we start, let’s quickly check out the requirements.
Requirements: The post assumes that your Azure AD user account is assigned any of the Admin roles as mentioned below to be able to perform the portal-end activities. Application Administrator Cloud Application Administrator Global Administrator
Table of Contents
Register an Application in Azure AD to connect to Microsoft Graph
Head over to the Azure AD portal and navigate to App registrations and create a New registration.
Next you have to
- give a Name to the app
- specify who can use the app as per your environment,
- provide a redirect URI, and finally
- click on Register.
Note: Though Redirect URI is optional, certain auth scenario requires a Redirect URI to be specified to return auth responses. We need not worry about name resolution as the URL does not needs to be resolved! As such, you may choose to enter in any random URL as the value. In our case, I have put http://localhost as per MS suggestion.
Once the application gets created, for now, take a note of the following details which you will require later
- Application (client) ID
- Directory (tenant) ID
Generate Client Secret for the Application
In the Azure AD portal and from within the App you just created, navigate to Certificates and secrets.
Notice that you have two options – New Client secret or Upload certificate. I will show you both the methods.
#1 – Generate Client Secret
- Click on the New client secret button. From the pop-up window that appears as shown,
- Provide a description,
- Set the expiration term, and
- Click on Add.
You would be returned to the same screen, but it will show you the Value for the Client secret you just generated. Make a note of it, as we will need it later.
IMPORTANT: Make a note of the Client secret Value before changing screens, as this is displayed one-time only. Later, the value will be masked and you would not be able to retrieve it. Only option would be to delete and create a new one.
#2 – Generate Client Secret based on Certificate
This is considered more secured and is mostly used with Application-level authorization scenarios. You can create a self-signed cert using the below sample PS code snippet.
# Your tenant name
$TenantName = "<tenantName>.onmicrosoft.com"
# Cert store you want the created certificate to be stored
$StoreLocation = "Cert:\CurrentUser\My"
# Expiration date of the new certificate (set to X=5 years from present date)
$ExpirationDate = (Get-Date).AddYears(5)
#Details with which to prepare the certificate
$CreateCert = @{
FriendlyName = "<Name_of_App_created_in_Azure>"
DnsName = $TenantName
CertStoreLocation = $StoreLocation
NotAfter = $ExpirationDate
KeyExportPolicy = "Exportable"
KeySpec = "Signature"
Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider"
HashAlgorithm = "SHA256"
}
# Create certificate
$Certificate = New-SelfSignedCertificate @CreateCert
# Set certificate path to store created cert
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint
# Export the certificate without the private key to the export path
$ExportPathCheck = "C:\Temp"
If (!(Test-Path -Path $ExportPathCheck)){
mkdir $ExportPathCheck
$CerOutPath = "C:\Temp\PowerShellGraphCert.cer"
Export-Certificate -Cert $CertificatePath -FilePath $CerOutPath | Out-Null
}
else
{
$CerOutPath = "C:\Temp\PowerShellGraphCert.cer"
Export-Certificate -Cert $CertificatePath -FilePath $CerOutPath | Out-Null
}
You will find the self-signed cert (.CER) generated in the path as defined in the variable $ExportPathCheck.
All you would need to do is
- head back to Azure AD portal > App Registrations and from within the App you just created,
- navigate to Certificates and secrets, and
- use the Upload certificate to upload the self-signed certificate you just generated.
You should be able to see the cert details such as thumbprint and cert validity period on the screen post upload.
Add API Permissions to the Application
- Next move on to API permissions within the Azure AD Application you created and click on Add a permission.
- Next you will be asked to choose the API that your app will work with. Choose Microsoft Graph.
- Next you will be prompted to choose the permission type – Delegated or Application
Use the Microsoft Graph beta and v1 reference documentation to identify the permissions required for the particular resources that your application (program/script) would be working with. Again, a security best practice is to try use the permission with least privilege.
Once you have identified all the permissions required for your application to work,
- select the Permission type and then select all the permission required for your application. You can use the search option.
Notice that some (majority) permissions will state that it requires Admin consent, meaning an Admin needs to approve before the permission is added to the application. As such, when you add the permission, you will see the status of the permission as Not Granted for Admin consent.
- Click on the Grant admin consent button. The status for each permission will change to Granted state.
NOTE #1: If your user account does not have the Application Admin/Cloud Application Admin/Global Admin role assigned, and you only work on script/programming end (likely a Developer), you would need to furnish the above details to someone whose account has any of the mentioned roles assigned, so that they can review and complete the steps till this.
NOTE #2: If your user account does not have the Application Admin/Cloud Application Admin/Global Admin role assigned, and the application will be running as on-behalf of your account with delegated permission, make sure you mention the same to the Admin so that your account is added as a user to the Application.
[OPT] Modify Application manifest in Azure to support Public Client connection
This step is only required if your app would be using either ROPC auth flow, Device code auth flow or Windows Integrated auth flow. Since we will be dealing with Authorization Code and Client Credential auth flows, this is not required for the purpose of this blog.
In the Azure portal, from the App Registrations screen, select your App and then go to Authentication and Enable “Allow public client flows.”
You can also do this by making a change in the application manifest – set “allowPublicClient” to true.
This is all required to be configured from the portal to register your app/script with the Microsoft Identity platform. With this, let’s now move on to the client-side.
MSAL Auth implementation in PowerShell to work with Microsoft GRAPH
In the previous post, we talked about the two most suitable MSAL supported Oauth2 auth flow methods for working with Microsoft Graph – Authorization Code and Client Credentials auth flow methods.
Here, let’s see how we can implement the two auth flow methods in PowerShell to obtain an Access Token from the Microsoft Identity platform to work with Microsoft Graph.
PowerShell code sample to implement Authorization Code Auth flow
#Define Client Variables Here
#############################
$clientId = "b3fac597-e86a-49a6-a42f-25bb250c7aa2"
$clientSecret = "X32-_Cpl8~_dH-0MeK8U6hvCQZs.H2DOHv"
$resource = "https://graph.microsoft.com"
$scope = "https://graph.microsoft.com/.default"
$redirectUri = "https://localhost"
#UrlEncode variables for special characters
###########################################
Add-Type -AssemblyName System.Web
$clientSecretEncoded = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$redirectUriEncoded = [System.Web.HttpUtility]::UrlEncode($redirectUri)
$resourceEncoded = [System.Web.HttpUtility]::UrlEncode($resource)
$scopeEncoded = [System.Web.HttpUtility]::UrlEncode($scope)
#Obtain Authorization Code
##########################
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=440;Height=640}
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=420;Height=600;Url=($url -f ($Scope -join "%20")) }
$url = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&redirect_uri=$redirectUriEncoded&client_id=$clientID&resource=$resourceEncoded&prompt=admin_consent&scope=$scopeEncoded"
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() | Out-Null
$queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
$output = @{}
foreach($key in $queryOutput.Keys){
$output["$key"] = $queryOutput[$key]
}
$regex = '(?<=code=)(.*)(?=&)'
$authCode = ($uri | Select-string -pattern $regex).Matches[0].Value
#Get Access Token with obtained Auth Code
#########################################
$body = "grant_type=authorization_code&redirect_uri=$redirectUri&client_id=$clientId&client_secret=$clientSecretEncoded&code=$authCode&resource=$resource"
$authUri = "https://login.microsoftonline.com/common/oauth2/token"
$tokenResponse = Invoke-RestMethod -Uri $authUri -Method Post -Body $body -ErrorAction STOP
PowerShell code sample to implement Client Credential auth using Self-Signed Certificate
#Define Client Variables Here
########################
$TenantName = "blueear.onmicrosoft.com"
$AppId = "b3fac597-e86a-49a6-a42f-25bb250c7aa2"
$Certificate = Get-Item Cert:\CurrentUser\My\9c2abfb10dc868e1eaf2e5c2bc7fb7f7ec496e93
$Scope = "https://graph.microsoft.com/.default"
#Create base64 hash of certificate
##################################
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
#Create JWT timestamp for expiration
####################################
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)
#Create JWT validity start timestamp
####################################
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)
#Create JWT header
#################
$JWTHeader = @{
alg = "RS256"
typ = "JWT"
x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}
#Create JWT payload
#################
$JWTPayLoad = @{
aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
exp = $JWTExpiration
iss = $AppId
jti = [guid]::NewGuid()
nbf = $NotBefore
sub = $AppId
}
# Convert header and payload to base64
#################################
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)
$JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)
# Join header and Payload with "." to create a valid (unsigned) JWT
######################################################
$JWT = $EncodedHeader + "." + $EncodedPayload
# Get the private key object of your certificate
######################################
$PrivateKey = $Certificate.PrivateKey
# Define RSA signature and hashing algorithm
#####################################
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
# Create a signature of the JWT
#########################
$Signature = [Convert]::ToBase64String(
$PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='
# Join the signature to the JWT with "."
###############################
$JWT = $JWT + "." + $Signature
# Use the self-generated JWT as Authorization to get the Access Token
##########################################################
$Header = @{
Authorization = "Bearer $JWT"
}
$Body = @{
client_id = $AppId
client_assertion = $JWT
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
scope = $Scope
grant_type = "client_credentials"
}
$authUri = "https://login.microsoftonline.com/common/oauth2/token"
$TokenResponse = Invoke-RestMethod -Header $Header -Uri $authUri -Method POST -Body $Body
PowerShell code sample to implement Client Credential auth using Client Secret
#Define Client Variables Here
#############################
$TenantName = "blueear.onmicrosoft.com"
$clientID = "b3fac597-e86a-49a6-a42f-25bb250c7aa2"
$clientSecret = "X32-_Cpl8~_dH-0MeK8U6hvCQZs.H2DOHv"
$Scope = "https://graph.microsoft.com/.default"
#Construct Auth call to get Access Token
#########################################
$Body = @{
Grant_Type = "client_credentials"
Scope = $Scope
client_Id = $clientID
Client_Secret = $clientSecret
}
$authUri = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
$TokenResponse = Invoke-RestMethod -Uri $authUri -Method POST -Body $Body
Client Credential Auth flow with Client Secret requires the shortest code and hence easier to implement than the rest.
Verify the Access Token from Auth Response
Irrespective of the Auth flow method used, the Auth response is stored in the variable $TokenResponse with which you can check the access token acquired.
Further, if you have the JWTDetails PS module installed, you can see the claims contained by the Access Token as shown below.
Now that your app has an access token, you can start using to build the actual part of your script.
To be Contd…
Note that the PowerShell code samples as shown above are derived from the examples already available in the internet and are not something not written by me. The purpose of reuse is to showcase the MSAL Auth implementation with PowerShell.
That was all for today. In the next post of this series, I will be showing you how to construct Graph API calls in PowerShell. Stay tuned!