Table of Contents
Microsoft Graph is here to unite Azure and Office 365 data under a single roof. It is a simple REST API and Microsoft provided many examples of how to use it, including an interactive Graph Explorer which allows us to discover the different methods.
Using the API is as simple as sending an HTTP request – for example, calling this method will return the details about the users in the directory:
In the Graph Explorer demo page, it all works fine, but as soon as we try to use the Graph API from outside the page, from another program or test application, we receive a “401 Unauthorized” exception.
What is an Access Tokens
As it turns out, in order to use any of the Microsoft Graph API, we need to let it know who we are – who is making the request.
Microsoft Graph API uses Bearer Authentication in order to validate the request, which means it expects to receive an authorization token (sometimes called a bearer token) together with the request. This token will contain, in a secured way, all the details about the requester.
Sending an authorization token with the request is a simple matter, all we need to do is to add an Authorization header to the request containing the word Bearer and our authorization token:
Authorization: Bearer <access_token>
There are several kinds of authorization tokens – Graph API requires an access token. The token itself is a look like a random base 64 string, something like below:
It is not important to understand the token format for now, only that once we get a valid access token, we can use it to access the information we need.
So how do we get the access token? That’s where things get little more complicated. In order to get a valid token for the Graph API, we need to use another Microsoft API: the Azure Active Directory (AAD) Services.
Azure Active Directory Services
Azure Active Directory is where all of our organization’s users are stored. Since the data we want to retrieve from the Graph API is usually related to specific users, it only makes sense that we need to use Azure Active Directory Services in order to retrieve a valid access token.
Microsoft AAD Services is based on the OAuth 2.0 protocol and act as an Identity Provider. Using these services, we can issue access tokens for the Graph methods (as well as id tokens and refresh tokens which are not in the scope of this article).
The Azure Active Directory Authorization endpoint has the following URL format:
https://login.microsoftonline.com/$tenant/oauth2/v2.0/token
$tenant can be:
- TenantID: for example: b22be9c4-7465-4f08-a59e-8854a346448a
- Initial domain name: for example bonben.onmicrosoft.com.
- Or the custom domain, for example duybao.me.
Meaning every tenant directory has its own URL. The directory name or id can be found in the Micrsoft Entra admin center.
So now that we know what the authorization endpoint URL is, what message do we need to send in order to get an access token?
Well, the answer for that is – it depends. The service supports several OAuth authentication flows, each suited for a different scenario and the kinds of information we have. Regardless of the kind of message we send, the response will always contain the Access Token.
But, before we can look at the different options, we first need to understand another important part of the puzzle. All of the different flows in Graph API have something in common – they all require a Client ID with a Client Secret (or client certificate). In order to get those, we first need to create an OAuth App.
Understanding OAuth Apps (Clients)
A very important concept in the OAuth world is the separation between users and clients. Users are the actual people who use our system. Clients are the applications they are using to do so. Why is this separation important? It’s all about regulating access to resources.
In the past, when applications wanted to access data in another system or database which required authentication it had two options:
- Pass on the current logged in user’s authentication.
- Impersonate a strong user.
Many times, the first option was not used – sometimes because it was complicated to perform (SSO is hard to get right), or the current user did not have enough permissions to perform the operation the application required.
This leaves the second option: impersonating a strong user. But which user to impersonate? Since we don’t want to use an existing user (which could lead to many issues), a dedicated user for the application needs to be created. Those users are often called System Accounts since they are used by the system and not actual human beings.
However, this pattern of using system accounts had many problems, for example:
- Password policy: The System Account had a password just like any other user, which caused problems if the normal password policy were applied to them. For example, the password would expire after some time, meaning all applications which depended on them would have failed.
- Managing: Users are usually managed by the organization’s IT department, meaning developers were dependent on them in order to create and manage System Accounts across different environments. This can cause operation delays and unexpected application shut-downs as a result of a random password change.
- Security: Since the application runs its code as a power-user, any vulnerability in the code could allow users to gain access to data they were not supposed to be able to access. This can be used for to perform Privilege escalation attacks. Because of that, it is usually considered dangerous to use code impersonation.
- No refined Access Control: Since the application is using a single System User for ALL users, it means that System Account has to have full access to ALL of the user’s data. This could lead to information leakage where users are accidentally exposed to other users data. Also, the users have no say in what kind of data a specific application can access – it is controlled by the system itself.
Because all of those issues, the OAuth protocol doesn’t use System Accounts in order to authenticate a client program. Instead of System Accounts, we now have OAuth Apps (clients).
In OAuth, when a client application wants to access a resource (for example our Graph API), the first thing it needs to do is to authenticate itself (meaning which client application is calling the service, not which user is using it). This is done by sending the Client ID and it’s matching Client Secret.
Registering an OAuth App
So where do we get that Client ID and Secret? We can get it by registering an OAuth app. You can follow this post to create an app registration on AzureAD.
Getting the Access Token
After we registered our OAuth App, got its Client ID and Secret, and configured its permissions, we can finally use AAD Services in order to get the Access Token.
In OAuth, there are several different ways to achieve access tokens, each suited for different a scenario. Those ways are called “grant flows,” and, according to the desired flow, a different message needs to be sent. Let’s review our different flows.
Get an Access Token from Client Credentials (Client Credentials Grant)
The most basic option is to use our Client ID and Secret in order to get an access token. For this, we need to send a POST message to our Azure Active Directory Authentication endpoint (which we talked about before) with following body parameters:
#Get access token
$clientId = "977a744d-efd6-4faa-967f-fc56bfaaca07"
$tenantId = "b22be9c4-7465-4f08-a59e-8854a346448a"
$clientSecret = "WQ~8Q~T0lWQYvz.xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientId
Client_Secret = $clientSecret
}
$connectGraph = Invoke-RestMethod -Uri $uri -Method POST -Body $Body
$token = $connectGraph.access_token
$headers = @{Authorization = "Bearer $token"}
PS C:\> $connectGraph
token_type : Bearer
expires_in : 3599
ext_expires_in : 3599
expires_on : 1697447553
not_before : 1697443653
resource : 00000002-0000-0000-c000-000000000000
access_token : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjlHbW...
This option is called Client Credentials Grant Flow and is suitable for machine-to-machine authentication where a specific user’s permission to access data is not required.
Get Access Token using the Microsoft Authentication Library (MSAL)
As the name indicates the module relies on MSAL. Furthermore, it implements an in-memory token cache to persist acquired tokens, optionally you can enable toke caching on your disk.
You can install the module on your machine with:
Install-Module -Name MSAL.PS -Scope CurrentUser
Now let’s have a look about the available options within Entra ID to obtain access tokens and some use cases:
No matter which option we choose to acquire tokens and want to interact with the Graph API we need an app registration.
The interactive authorization code flow pops-up either a login or browser window and you are prompted to enter your Entra ID username and password.
$connectionParams = @{
'TenantId' = 'c032627b-6715-4e39-9990-bcf48ee5e0c5'
'ClientId' = 'ffb97f4f-cd58-4e4d-95ac-17081063c20b'
'Interactive' = $true
}
Get-MsalToken @connectionParams
AccessToken : eyJ0eXAiOiJKV1QiLCJub25jZSI6IjRGbXR5ekFyZlhzeWVUWmJYOW....
IsExtendedLifeTimeToken : False
UniqueId : 647fea69-afca-4001-af45-f0cc82a2fa41
ExpiresOn : 10/16/2023 4:51:53 PM +00:00
ExtendedExpiresOn : 10/16/2023 4:51:53 PM +00:00
TenantId : c032627b-6715-4e39-9990-bcf48ee5e0c5
Account : Account username: [email protected] environment login.windows.net home
account id: AccountId:
647fea69-afca-4001-af45-f0cc82a2fa41.c032627b-6715-4e39-9990-bcf48ee5e0c5
IdToken : eyJ0eXAiOiJKV....
Scopes : {profile, openid, email, https://graph.microsoft.com/User.Read...}
CorrelationId : 4159b1b8-0441-456f-9bbe-cd1c31415acc
TokenType : Bearer
ClaimsPrincipal : System.Security.Claims.ClaimsPrincipal
AuthenticationResultMetadata : Microsoft.Identity.Client.AuthenticationResultMetadata
User :
A client secret allows unattended authentication and the secret needs to be added to your app registration. The commandlet requires the client secret as a secure string parameter.
$connectionParams = @{
'TenantId' = 'c032627b-6715-4e39-9990-bcf48ee5e0c5'
'ClientId' = '890b1702-32f8-4cfc-ae54-5ec3cba3fc30'
'ClientSecret' = 'Icxxxxxxxxxxxxxxxxxxxx' | ConvertTo-SecureString -AsPlainText -Force
}
Get-MsalToken @connectionParams
Certificates also allow unattended authentication. The certificate and the corresponding private key need to be present in an accessible store.
$thumbprint = Get-Item -Path 'Cert:\CurrentUser\My\E4F63F3AEF2EB49420B5B6FF2617619BC581C818'
$connectionParams = @{
'TenantId' = 'c032627b-6715-4e39-9990-bcf48ee5e0c5'
'ClientId' = '890b1702-32f8-4cfc-ae54-5ec3cba3fc30'
'ClientCertificate' = $thumbprint
}
Get-MsalToken @connectionParams
Building a request header
To actually use the acquired access token we need to build a request header that we include in http requests to the Graph API. A PowerShell object instantiated from the Get-MsalToken commandlet exposes a method called CreateAuthorizationHeader() to include the Bearer token in the request header you use for subsequent requests:
#Get access token using MSAL
$clientId = "ffb97f4f-cd58-4e4d-95ac-17081063c20b"
$tenantId = "c032627b-6715-4e39-9990-bcf48ee5e0c5"
$clientSecret = "xxxxxxxxxxxxxxxxxx"
$secureSecret = $clientSecret | ConvertTo-SecureString -AsPlainText -Force
$MSToken = Get-MsalToken -ClientId $clientId -TenantId $tenantId -ClientSecret $secureSecret
$token = $MSToken.AccessToken
#Creating the request headers
$headers = @{Authorization = "Bearer $token"}
PS C:\> $headers
Name Value
---- -----
Authorization Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ii1oWm95WXlmTm5YaGZ....
Conclusion
This very detailed post shows you through different ways to obtain access tokens for your next PowerShell automation with the Microsoft Graph API. As a takeaway we always recommend using the MSAL.PS PowerShell module because this will save you lots of time instead of writing custom code to acquire access tokens.
Furthermore, for unattended scenarios I always recommend using certificates over client secret because they are better protected instead of a clear text client secret.
Not a reader? Watch this related video tutorial: