Understanding What’s in an Entra ID Access Token

Critical Piece When Connecting to the Microsoft Graph

By now, most people who write PowerShell code to interact with Microsoft 365 workloads understand that sometimes it’s necessary to use Microsoft Graph API queries instead of “pure” PowerShell cmdlets. The Graph queries are usually faster and more reliable when retrieving large quantities of data, such as thousands of Microsoft 365 Groups. Over the last few years, as people have become more familiar with the Microsoft Graph, an increased number of scripts have replaced cmdlets with Graph queries. All these scripts use Entra ID (Azure AD) access tokens, as does any utility which interacts with the Microsoft Graph, like the Graph Explorer (Figure 1).

The Graph Explorer displays its Azure AD access token
Figure 1: The Graph Explorer displays its access token

In the remainder of this article, I explore what an Entra ID access token contains.

The Need for Access Tokens

Graph queries need authentication before they can run and the Graph API uses modern authentication. Entra ID registered applications bridge the gap between PowerShell and the Graph. The apps hold details used during authentication such as the app name, its identifier, the tenant identifier, and some credentials (app secret or certificate. The app also holds permissions granted to access data through Graph APIs and other APIs. When the time comes to authenticate, the service principal belonging to an app uses this information to request an access token from Entra ID. Once Entra ID issues the access token, requests issued to the Invoke-RestMethod or Invoke-WebRequest cmdlets can include the access token to prove that the app has permission to access information.

At first glance, an access token is a confused mass of text. Here’s how PowerShell reports the content of an access token:

eyJ0eXAiOiJKV1QiLCJub25jZSI6IlFQaVN1ck1VX3gtT2YzdzA1YV9XZzZzNFBZRFUwU2NneHlOeDE0eVctRWciLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCIsImtpZCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9iNjYyMzEzZi0xNGZjLTQzYTItOWE3YS1kMmUyN2Y0ZjM0NzgvIiwiaWF0IjoxNjQ0ODQ1MDc3LCJuYmYiOjE2NDQ4NDUwNzcsImV4cCI6MTY0NDg0ODk3NywiYWlvIjoiRTJaZ1lEaW1McEgwTSt5QTk5NmczbWZUUXlYN0FBPT0iLCJhcHBfZGlzcGxheW5hbWUiOiJHZXRUZWFtc0xpc3QiLCJhcHBpZCI6IjgyYTIzMzFhLTExYjItNDY3MC1iMDYxLTg3YTg2MDgxMjhhNiIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2I2NjIzMTNmLTE0ZmMtNDNhMi05YTdhLWQyZTI3ZjRmMzQ3OC8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6IjM4NTRiYjA4LTNjMmMtNGI1Ny05NWZjLTI0ZTA3OGQzODY4NSIsInJoIjoiMC5BVndBUHpGaXR2d1Vva09hZXRMaWYwODBlQU1BQUFBQUFBQUF3QUFBQUFBQUFBQmNBQUEuIiwicm9sZXMiOlsiVGVhbVNldHRpbmdzLlJlYWRXcml0ZS5BbGwiLCJUZWFtTWVtYmVyLlJlYWQuQWxsIiwiR3JvdXAuUmVhZC5BbGwiLCJEaXJlY3RvcnkuUmVhZC5BbGwiLCJUZWFtLlJlYWRCYXNpYy5BbGwiLCJUZWFtU2V0dGluZ3MuUmVhZC5BbGwiLCJPcmdhbml6YXRpb24uUmVhZC5BbGwiLCJBdWRpdExvZy5SZWFkLkFsbCJdLCJzdWIiOiIzODU0YmIwOC0zYzJjLTRiNTctOTVmYy0yNGUwNzhkMzg2ODUiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiRVUiLCJ0aWQiOiJiNjYyMzEzZi0xNGZjLTQzYTItOWE3YS1kMmUyN2Y0ZjM0NzgiLCJ1dGkiOiI3RVkyWnVXV2JFYVF0T3piVVlwOUFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyIwOTk3YTFkMC0wZDFkLTRhY2ItYjQwOC1kNWNhNzMxMjFlOTAiXSwieG1zX3RjZHQiOjEzMDI1NDMzMTB9.N9yvmkCedti2fzT44VfBkN7GvuCInrIgiMgNxdyZeAyxnbdZjEhxHmNdU6HLLHQ3J-GonpPdt28dKwYxgLcrSibGzSPVHddh6MDPYutSwfIxh2oRanxhgFOWVJADfbFoCxsRFDhKJNT39bsauIUiRNzGzbb6dvWuZQ8LrgWjZzjae2qxVxj9jvYgjXEypeYZgLvPOzJiBCuluAMH3TjPuS-CuglFK_edn4CS-ztCwM0hmDFD5BLNZqng5P2KqGTEgjkMKoyIJ8yTGBJpASfdqqEFqWzQwcQ9ese924qNC3hJR_5TWHp2Fl73bpdhwBHRL5UwGTPi9_ysYdndKhXwgA

Deciphering an Access Token

Access tokens issued by Entra ID comply with the OAuth 2.0 bearer token standard (RFC6750) and are structured as JSON-formatted Web Tokens. We can’t see the JSON content because it is base64Url encoded and signed. However, if you paste the token into a site like https://jwt.ms/, the site will decrypt the list of claims included in the token and we’ll see something like the details shown below for the access token featured above:

{ "typ": "JWT", 
"nonce": "gq3zmJhybfXGDGqt6RO2PX9s0cimmRpSRrTO90sQ4w4", 
"alg": "RS256",
 "x5t": "Mr5-AUibfBii7Nd1jBebaxboXW0", 
"kid": "Mr5-AUibfBii7Nd1jBebaxboXW0" 
}.
{ "aud": "https://graph.microsoft.com", 
"iss": "https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/", 
"iat": 1644833772, 
"nbf": 1644833772,
 "exp": 1644837672,
 "aio": "E2ZgYJif1+eocevtzqRIrgDGA2V3AQ==",
 "app_displayname": "ReportDLs", 
"appid": "76c31534-ca1f-4d46-959a-6159fcb2f77a", 
"appidacr": "1",
 "idp": "https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/", 
"idtyp": "app",
 "oid": "4449ce36-3d83-46fb-9045-2d1721e8f032",
 "rh": "0.AVwAPzFitvwUokOaetLif080eAMAAAAAAAAAwAAAAAAAAABcAAA.",
 "roles": 
[ "Group.Read.All", "Directory.Read.All", "User.Read.All" ],
 "sub": "4449ce36-3d83-46fb-9045-2d1721e8f032", 
"tenant_region_scope": "EU", 
"tid": "a662313f-14fc-43a2-9a7a-d2e27f4f3478",
 "uti": "BU1RVc7mHkmBq2FMcZdTAA", 
"ver": "1.0", 
"wids": [ "0997a1d0-0d1d-4acb-b408-d5ca73121e90" ],
 "xms_tcdt": 1302543310 
}
.[Signature]

The deciphered token divides into three parts: header, payload, and signature. The aim of a token is not to hide information, so the signature is not protected by encryption. Instead, it’s signed using a private key by the issuer of the token. Details of the algorithm and private key used to sign an access token are in its header. An application can validate the signature of an access token if necessary, but this is not usually done when running a PowerShell script. The payload is the location for the claims made by the token and is the most interesting place to check.

Another way to check what’s in an access token is to use the JWTDetails PowerShell module, which is available in the PowerShell Gallery. To install this (very small) module, run:

Install-Module -Name JWTDetails -RequiredVersion 1.0.0 -Scope AllUsers

Afterward, you can examine a token with the Get-JWTDetails cmdlet. Here’s an example revealing that the access token issued to an app allows it to access Exchange Online using the IMAP4 or POP3 protocols:

Get-JWTDetails -Token $Token

aud             : https://outlook.office.com
iss             : https://sts.windows.net/b662313f-14fc-43a2-9a7a-d2e27f4f3478/
iat             : 1671891468
nbf             : 1671891468
exp             : 1671895368
aio             : E2ZgYDAQS/prW6b0Zsah6KMXtnTEAQA=
app_displayname : POP3 and IMAP4 OAuth 2.0 Authorization
appid           : 6a90af02-6ac1-405a-85e6-fb6ede844d92
appidacr        : 1
idp             : https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/
oid             : b7483867-51b6-4fdf-8882-0c43aede8dd5
rh              : 0.AVwAPzFitvwUokOaetLif080eAIAAAAAAPEPzgAAAAAAAABcAAA.
roles           : {POP.AccessAsApp, IMAP.AccessAsApp}
sid             : 1475d8e7-2671-47e9-b538-0ea7b1d43d0c
sub             : b7483867-51b6-4fdf-8882-0c43aede8dd5
tid             : a662313f-14fc-43a2-9a7a-d2e27f4f3478
uti             : COCw22GGpESVXvfdhmEVAQ
ver             : 1.0
wids            : {0997a1d0-0d1d-4acb-b408-d5ca73121e90}
sig             : PdScMpYqwA25qJL1z8q589sz/Ma5CGQ4ea9Bi0lnO2yByrIs530emYPnFPfQNN9EPBIvv4EaAoTLomrw4RMBWYoQSAgkBUXVrYGnC
                  jzAU6a2ZNZgo7+AORHk4iyLO0FpbLEaMJvCvI5vWhP9PHOxnGLcIsCbOmyrCK6lxxIKtBx851EpLrhpyvJ3p05NSw0D/mKzXPRKtc
                  rzQcUwECxOUugbm1zdq8JaE/PmSggBb87VZy7p1S2BXhxQZ5QU17JeIADyhCGm1Ml+avuIHsVS2iat/LPEi/nktbrXMcOzROpUKyZ
                  /7uVhxQ0cscJ6WGxbd+zJm36s25Yp1vMzSHaRxQ==
expiryDateTime  : 24/10/2022 15:22:48
timeToExpiry    : 00:59:34.7611307

Claims and Scopes

The list of claims in the access token includes simple claims and scopes (groups of claims). A claim is an assertion about something related to the token. In this case, the claims tell us details like:

  • The tenant (tid).
  • The intended consumer of the token (aud): https://graph.microsoft.com.
  • The app name (app_displayname).
  • The app identifier (appid).
  • The security token service (STS) responsible for issuing the token (iss): https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/.
  • The generation time for the token (iat).
  • The time when the token expires (exp). All dates are in Unix epoch time, so 1644837672 means 11:21:12 GMT on February 14, 2022. By default, access tokens issued by Entra ID last one hour, except those used by applications which support continual access evaluation (CAE), where Entra ID issues 28-hour access tokens because it can terminate access at any time and force the user to reauthenticate should a critical user event (like a password change) happen.
  • The identifier for the object in the Microsoft identity system used for authentication (oid). In this case, the script uses a registered Entra ID app, so the value is the service principal for the app. You can test this by running the Get-MgServicePrincipal cmdlet from the Microsoft Graph PowerShell SDK:

Get-MgServicePrincipal -Filter "Id eq '4449ce36-3d83-46fb-9045-2d1721e8f032'"

DisplayName Id                                   AppId                                SignInAudience ServicePrincipalTy
                                                                                                     pe
----------- --                                   -----                                -------------- ------------------
ReportDLs   4449ce36-3d83-46fb-9045-2d1721e8f032 77c31534-ca1f-4d46-959a-6159fcb2f77a AzureADMyOrg   Application

Scopes are a logical grouping of claims, and they can serve as a mechanism to limit access to resources. The roles claim contains a scope of Graph API permissions starting with Group.Read.All and ending with User.Read.All. We therefore know that this app has consent from the organization to use the permissions stated in the scope when it executes Graph API queries. The list of permissions is enough to allow the PowerShell script (in this case, one to generate a report of distribution list memberships) to query the Graph for a list of all groups and read the membership of each group.

From bitter experience, I know how easy it is to get Graph permissions wrong. One way to check is sign into the Graph Explorer and run the query (here’s an example) to check what permissions the Explorer uses to execute the query. However, you can also dump the access token to check that the set of permissions in the access token matches what you expect. It’s possible that you might have requested some application permissions for the app and failed to gain administrator consent for the request, meaning that the access token issued to the app by Entra ID won’t include the requested permissions.

Using the Access Token

Once we’re happy that we have a good access token, we can use it with Graph queries. Here’s how to fetch the list of distribution groups in a tenant. The access token is included in the $Headers variable passed to the Invoke-RestMethod cmdlet.

$Headers = @{Authorization = "Bearer $token"}

$Uri = "https://graph.microsoft.com/V1.0/groups?`$filter=Mailenabled eq true and not groupTypes/any(c:c+eq+'Unified')&`$count=true"
[array]$DLs = (Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get -ContentType "application/json")
$DLs = $DLs.Value

And if everything goes to plan, we should have a set of distribution lists to process. If not, it’s bound to be a problem with your access token, so it’s time to return to square one and restart the acquisition process.


Learn more about how Office 365 really works on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

6 Replies to “Understanding What’s in an Entra ID Access Token”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.