Connect to the Graph in a Secure and Powerful Way

Updated: 1 August 2023

In a previous article, I discuss using cmdlets from the Microsoft Graph SDK for PowerShell to replace license management cmdlets from the Azure AD module in advance of their retirement on June 30, 2022 (update: Microsoft has moved this date out to March 2024). As it turns out, converting a couple of Azure AD cmdlets by replacing them with cmdlets like Get-MgUser and Set-MgUserLicense isn’t particularly difficult (if it was, I wouldn’t be able to do it). However, introducing a new module into the mix of PowerShell used in production environments always creates some questions and concerns, not least of which is security.

There’s a right and a wrong way to use the Graph SDK cmdlets in testing and production environments. In a nutshell, running the cmdlets interactively is straightforward, but could lead to a problem with an over-permissioned service principal (app). It’s all to do with the way the Graph SDK connects and the permissions it uses. Let’s examine why.

Read more: Microsoft Is Moving the Office 365 Service Communications API to the Graph

Connecting to the Graph SDK

The first step in any use of the Graph SDK is to connect to the Graph using the Connect-MgGraph cmdlet. When you run Connect-MgGraph to connect to the Graph, it’s wise to specify the identifier of the tenant to which you want to connect.

Connect-MgGraph -TenantId "828e1143-88e3-492b-bf82-24c4a47ada63"

If you don’t specify a tenant, Connect-MgGraph will choose the last tenant you signed into during a session (which might not be the one you want to connect to). I discovered this when I connected to the Graph and discovered that the data used belonged to my development tenant. I noticed then, but it’s possible that someone might miss this elsewhere, so make it a habit to connect with the tenant identifier.

A session lasts until you run Disconnect-MgGraph (see below) and can be reinitiated multiple times over days by running Connect-MgGraph. Behind the scenes, the Graph SDK keeps an encrypted token cache and will refresh the token as needed to allow you to work with Graph commands.

Permissions

After making a successful connection, the session signs in as your account. The permission scope for the connection comes from the service principal (enterprise app) registered in Azure AD for the SDK. If you’ve never signed in with the Graph SDK before, the SDK creates an enterprise app called Microsoft Graph Command Line Tools with an AppId of 14d82eec-204b-4c2f-b7e8-296a70dab67e and requests a limited set of permissions (Figure 1). If you’re an administrator, you can grant consent for these permissions on behalf of the organization. To ease the reasonable suspicions of vigilant administrators, it would be nice if the service principal showed up as verified, but that’s not currently the case.

Granting initial consent for the Graph PowerShell SDK
Figure 1: Granting initial consent for the Graph PowerShell SDK

The permissions consented for the Microsoft Graph PowerShell SDK are held by the enterprise app’s service principal.

Gathering New Permissions

As people begin executing Graph SDK commands using the interactive client, they will need consent for the permissions needed to run the commands. For example, to use the Get-MgUser cmdlet to retrieve a set of Azure AD accounts, a user needs permission to read directory information, so they might request the permissions using the Scope parameter when making the connect as follows:

$RequiredScopes = @("Directory.AccessAsUser.All", "Directory.ReadWrite.All")
Connect-MgGraph -Scopes $RequiredScopes

Connecting with a scope which includes permissions not held by the enterprise app causes Azure AD to prompt for consent for the permissions not inherited from the service principal (Figure 2).

Azure AD prompts to grant consent for more permissions for the Graph SDK service principal
Figure 2: Azure AD prompts to grant consent for more permissions for the Graph SDK service principal

Like other apps which use the Graph APIs, an administrator consent to the requested permissions on behalf of the organization. This adds the extra permissions to the set held by the service principal. If an administrator does not grant consent, the requested permissions are available (subject to the administrative roles held by the signed-in account) for the session, but don’t join the set held by the service principal.

Over time, the permissions held by the SDK’s service principal will be those granted at the initial time of consent plus any other permissions granted subsequently as people work with the interactive client. In other words, the service principal collects aggregated permissions over time. For this reason, it’s not recommended to use the Graph SDK cmdlets interactively because if you do, over time a distinct possibility exists that the service principal will become very over-permissioned and therefore becomes a security risk. This is the kind of issue highlighted by App Governance for Cloud App Security (or in the home-grown solution explained in this article). In addition, you should pay attention to apps assigned high-priority permissions because they’re the ones that might be either planted or exploited by attackers.

The only resolution for an over-permissioned service principal is its removal and recreation, at which time an administrator can grant consent for limited permissions to the new service principal. Here’s how to remove the service principal using Graph SDK cmdlets (naturally):

$Sp = Get-MgServicePrincipal -Filter "AppId eq '14d82eec-204b-4c2f-b7e8-296a70dab67e'"
Remove-MgServicePrincipal -ServicePrincipalId $Sp.Id

V2.0 of the Microsoft Graph PowerShell SDK removes the need to switch profiles to use beta cmdlets. Instead, the beta cmdlets are in a separate module.

Reporting Your Connection

To check that you’re connected to the right tenant with the right profile and permissions, we can extract information about the tenant with the Get-MgOrganization cmdlet, the current connection with the Get-MgContext cmdlet, and the profile used with the Get-MgProfile cmdlet and display some useful information:

$Details = Get-MgContext
$Scopes = $Details | Select -ExpandProperty Scopes
$Scopes = $Scopes -Join ", "
$OrgName = (Get-MgOrganization).DisplayName
Clear-Host
Write-Host "Microsoft Graph Connection Information"
Write-Host "--------------------------------------"
Write-Host " "
Write-Host ("Connected to Tenant {0} ({1}) as account {2}" -f $Details.TenantId, $OrgName, $Details.Account)
Write-Host "+-------------------------------------------------------------------------------------------------------------------+"
Write-Host ("The following permission scope is defined: {0}" -f $Scopes)
Write-Host ""

Microsoft Graph Connection Information
--------------------------------------

Connected to Tenant a662313f-14fc-43a2-9a7a-d2e27f4f3475 (Office 365 for IT Pros) as account Global.Administrator@office365itpros.com
+-------------------------------------------------------------------------------------------------+
The following scopes are defined: Directory.AccessAsUser.All, Directory.ReadWrite.All, openid, profile, User.Read, email, Group.Read.All, Group.ReadWrite.All 

The permissions listed above include those inherited from the service principal and any others requested by the user for the session.

Disconnect When You’re Done

When you’re finished interacting with the Graph, remember to close off the session by running Disconnect-MgGraph to sign the session out from the Graph. Disconnecting the session removes the encrypted token cache and prevents a session from being reinitialized.

Disconnect-MgGraph

App-Only Access for Production Use

The description above covers an interactive session. This is a good way to get to know the Graph SDK cmdlets and debug scripts in preparation for operational use. However, when you run SDK cmdlets interactively, you’re limited to delegate permissions. In other words, you can access items that you can interact with through Microsoft 365 apps, but you can’t access items owned by other users. For instance, you can read items in your mailbox but not in another mailbox. If you want to run the SDK with application permissions, you can do so with an app or in Azure Automation (using a RunAs account or managed identity).

Because of the issues with consent and the service principal, Microsoft recommends that operational scripts use registered Azure AD apps with certificate-based authentication (app-only access) or a managed identity. You can certainly write and test scripts using the interactive client, but once the code is complete, it’s time to run it using its own app with a tightly scoped permission set. This approach means that you can restrict the permissions assigned to apps to only the set needed by the processing done by the script and restrict those who can use the app to a selected set of accounts. The downside of using separate apps with scoped permissions is that over time you might accumulate many registered apps in Azure AD which need to be managed.

The alternative is to use Azure Automation with managed identities to run scripts that use SDK cmdlets. See this article for more information.

Some Issues for Microsoft to Solve

Having a service principal which diligently gathers permissions on an ongoing basis doesn’t seem to be a good idea. In fact, it’s a lousy idea. Microsoft needs to come up with a better way of allowing administrators to run Graph SDK cmdlets interactively without creating a security problem. Deleting the security principal when Disconnect-MgGraph is run one way to zeroize permissions, but it’s a poor solution. Allowing people to hard code usernames and passwords or use app secrets in scripts to authenticate access to the Graph is also not a direction to take given the current threat landscape.

The fact of the matter is that tenant administrators will forget about the permission accumulation issue unless they check service principals. It’s sad but true, and due to the demands of other work. People won’t run Disconnect-MgGraph either for the same reason as cmdlets like Disconnect-MicrosoftTeams and Disconnect-ExchangeOnline are ignored.

The Graph SDK is an excellent idea because it isolates PowerShell users from many of the complexities involved in using Graph APIs. It’s still a work in progress, but if Microsoft wants to make the Graph SDK a go-to tool for tenant administrators, they need to solve the permissions issue. And soon (before that deprecation deadline!)

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Robert

    I cant believe Microsoft completely disabled the Set-MSOLUserLIcnese options and is expecting everyone to just Graph? What Microsoft? was it to easy to use the MSOL cmdlets?

    1. Avatar photo
      Tony Redmond

      The MSOL cmdlets are based on an old (first generation) implementation of licensing technology created when Office 365 was first launched. Things are very different today. The suite supports over 400 million users and there are many more licenses and service plans in use. Microsoft moved to a new licensing plaform and that’s when the MSOL and AzureAD licensing cmdlets stoppe working.

  2. Harry

    Hi sir,

    How create new user with details at a time. Please share PowerShell script.

    1. Email id and license, first name ,last name ,display name, title, phone number
    2.Reporting manager
    3.Groups
    4.MFA

  3. Petey

    If i have a partner center account, how can i connect and access all my customers using graph api powershell sdk?

    1. Avatar photo
      Tony Redmond

      Use Connect-MgGraph -TenantId xxx-xxx-xxx-xxx (insert tenant identifier of the tenant you want to connect to)

      1. Ricardo Goncalves

        Hi Tony

        I am new to Microsoft Graph and am hoping you could maybe assist me.
        I am looking for a script to be able to remove O365 License from bulk users by importing a CSV file.
        Is this possible to do.
        I have found a way to add a O365 License to bulk users via a CSV file but am battling to find a way to remove the License from bulk users
        Your assistance would be greatly appreciated.

      2. Chris

        Hi Tony,
        How does this work with GDAP as I receive the error AADSTS90099 when trying to connect to my client tenants?
        .
        AADSTS90099: The application ’14d82eec-204b-4c2f-b7e8-296a70dab67e’ (Microsoft Graph Command Line Tools) has not been authorized in the tenant ”. Applications must be authorized to access the customer tenant before partner delegated administrators can use them.
        .
        In the customer tenant, the Enterprise Application is there and is being used by customer accounts, but is there something that needs to be done to enable for partner GDAP admins?

        1. Avatar photo
          Tony Redmond

          The error indicates that the service principal for the Microsoft Graph Command Line Tools enterprise app has not been authorized in the tenant. I don’t know what process apps go through to allow them to run in the context of a GDAP tenant. I guess it is something covered in the GDAP documentation https://learn.microsoft.com/en-us/partner-center/gdap-introduction?WT.mc_id=M365-MVP-9501 but as I don’t have a GDAP tenant to play with, I can’t test or do anything else.

  4. Pavel

    HI
    im using

    $Mbx = Get-ExoMailbox -Identity $Email | Select ExternalDirectoryObjectId, DisplayName, UserPrincipalName

    # Get Token for Graph Api
    $MsalResponse = Get-MsalToken -ClientId $appId -TenantId $tenantid -ClientSecret (ConvertTo-SecureString $appSecret -AsPlainText -Force) -Scopes “https://graph.microsoft.com/.default” -ForceRefresh
    $GraphToken = $MsalResponse.AccessToken

    # Connect Graph API
    Connect-MgGraph -AccessToken $GraphToken

    [array]$ContactsFolders = Get-MgUserContactFolder -UserId $Mbx.UserPrincipalName
    $ContactFolder = $ContactsFolder | Where-Object {$_.DisplayName -eq “Contacts”}

    but getting empty value for $ContactsFolders?
    Any idea ?

    1. Avatar photo
      Tony Redmond

      Are you connecting to the Graph SDK interactively? If so, you use delegate access and can only work with your own data.

      Seeing you’re getting an access token, why don’t you use the Graph API? If the client app has consent, you’ll be able to access the contact folder in any mailbox.

      1. Pavel

        Hi Tony
        thanks for you answer. I have registered app with full permission of application with secret key. How to use Graph API for getting contact from contact folder and manage them ? (create,update,delete contact ?) im beginner with graph api. Many thanks Pavel

  5. Brian

    Hi Tony, I’m able to create an app registration in order to access email messages. The problem for me is that once I do this, I can access any email account in the tenant. Is there a way to restrict the app to only be able to access a specific email account? I need to create a non-interactive script to download attachments from a specific email account but don’t want the the clientId/secret credentials in the script to have access to any other email account. Thanks, Brian.

      1. Brian

        Thank you very much! That worked and my app is now only able to read the email of a single account.

  6. Essi

    Hello Tony,

    It is not possible to get Autopilot event with Connect-MgGraph in Azure Automation account.
    Is this maybe something yoy have tried?

  7. Karen

    Hi Tony,
    I want know cmdlets that is reset user password and don’t require update password when that user sign in with that password.
    Also, I want do this with MgGraph.
    This is my version of reset password in MgGraph
    $user = “User Principal Name”
    >>
    >> $method = Get-MgUserAuthenticationPasswordMethod -UserId $user
    >>
    >> Reset-MgUserAuthenticationMethodPassword -UserId $user -AuthenticationMethodId $method.id -NewPassword “New Password”

    But I can’t don’t require update password.
    pls give some help for me.

    Sorry for poor English. I’m japanese.
    Thx

    1. Avatar photo
      Tony Redmond

      A quick check against the PowerShell chapter in the Office 365 for IT Pros eBook shows that you need to set up a password profile and then run Update-MgUser.

      $NewPassword = @{}
      $NewPassword[“Password”]= “!NewPassword2022!”
      $NewPassword[“ForceChangePasswordNextSignIn”] = $False
      Update-Mguser -UserId Terry.Hegarty@Office365itpros.com -PasswordProfile $NewPassword

      Because the password profile ForceChangePasswordNextSignIn setting is False, the user is not forced to reset their password after logging in with the new password.

      1. Karen

        Thanks Tony.
        It’s completely worked for me!!!
        I’ll check the PowerShell chapter in the Office 365 for IT Pros eBook rn.

  8. Eriq VB

    Anybody know any way to connect to multiple tenants at the same time with powershell graph? There’s no -Prefix parameter in Connect-MgGraph so even if you could i’m not sure how you’d differentiate between calls.
    With Exchange powershell you could prefix all the commands and have Get-OrgAMailbox and Get-OrgBMailbox.

    I’m looking at a case of needing to read data from one tenant and update data in another.

    1. Avatar photo
      Tony Redmond

      You can’t. You can switch from one tenant to another by running Connect-MgGraph -TenantId (always run Disconnect-MgGraph first) but there’s no notion of maintaining multiple connections to different tenants concurrently.

  9. Albert

    Tony,

    Thank you for sharing, would it be possible to grant read-only (Azure Global Reader) to the MsGraph?

    This is so the read-only script and lower-tier users can learn about Azure &M365 environments scripting safely.

  10. Jamie

    Hi Tony,

    Thanks so much for the great write up. Regarding this section in your article:

    “If an administrator does not grant consent, the requested permissions are available (subject to the administrative roles held by the signed-in account) for the session, but don’t join the set held by the service principal.”

    My testing seems to indicate that an Application Administrator *does* need to grant consent when a new scope is specified (or the requesting user needs to be an Application Administrator themselves). I would love it if I could use the PS Graph SDK under my user (which at my org has User Administrator among other things but NOT Application Administrator) and not have to request admin consent every time I request a scope that my user has permissions for, but for which the Enterprise App does not.

    Do you know a way I can achieve this?

    1. Avatar photo
      Tony Redmond

      No, I don’t.

      But the Graph SDK is in a state of change with new versions appearing monthly, so what happens in one version might be different in the next. The important point to make is that the SDK is not really designed for interactive sessions, so I would not give “normal” users access to SDK interactive sessions because they then gain access to all the permissions held by the service principal. In many cases, dedicated registered apps with defined permissions are a better choice for lightly-permissioned administrators to work with.

      1. Jamie

        Thanks again Tony, really appreciate your time and expertise.

        I haven’t tested this, but I would have thought that because the permissions are delegated, even if the Enterprise App has permission for a given scope, the user requesting it would also need that permission. Or is that not the case?

        Regarding your thoughts on using the PS Graph SDK interactively, it’s a fair point but if MS are looking to retire the Msol and AzureAD cmdlets I think interactive use is quite likely for one-off administrative scenarios that would be laborious via the various web interfaces. What do you think?

        1. Avatar photo
          Tony Redmond

          Yes, the permissions are delegated when you do interactive logins. Application permissions are only available when you have separate apps and can assign application permissions to those apps. So I think tenant administrators will still have the need to perform interactive queries and fix-ups, but if you’re handing things over to other people to do day-to-day maintenance, maybe it’s better to have scripts that use the registered apps?

          1. Jamie

            Fair points. Lots to think about. Look forward to your future posts on the topic!

            Regards,

            Jamie

  11. George

    Hi Tony,

    Nice Article! What’s the “June 30, 2022 deadline” ?

    1. Avatar photo
      Tony Redmond

      It was the original date for the retirement of the Azure AD and MSOL PowerShell modules. That date is now shifted to early 2023 with the exception of the license management cmdlets, which will stop working on 26 August 2022 when Microsoft 365 moves to a new license management platform.

  12. Christophe Barneaud

    HI Tony,

    My powershell script is connected to Graph API using a secretkey with a registered app

    Modules Microsoft.Graph.Authentication and Microsoft.Graph.Identity.DirectoryManagement are successfully imported.
    use application permissions Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All for Get-MgOrganization.

    I successfully could connect to Microsoft-graph but get this error

    Get-MgOrganization : Authentication needed, call Connect-MgGraph
    when running my script

    Thanks

    https://docs.microsoft.com/en-us/graph/api/organization-get?view=graph-rest-1.0&tabs=powershell

    Best regards

    1. Avatar photo
      Tony Redmond

      Maybe you haven’t disconnected from your previous Graph session and the context you’re using is that session rather than the one you want to load?

  13. Kevin Giertz

    Hi Tony,

    Got any updates regarding the issues you raised for Microsoft to solve?

    1. Avatar photo
      Tony Redmond

      No. Things take time. Change in technology take even longer.

  14. wuyg

    Hi Tony. Great article! Do you know if the app-only authentication of Graph SDK can be used without certificate? I would like to use it in a DevOps pipeline. Depending on a certificate makes the script quite challenging in this scenario.

    1. Steve Johnson

      It’s entirely possible to use app-only auth using a certificate in an Azure DevOps pipeline.

      Make sure the certificate is stored in the Secure files library in your pipeline and then in your script you can authenticate using the following:

      $mgCertPath = “$env:AGENT_TEMPDIRECTORY/”
      $mgSecurePassword = ConvertTo-SecureString -AsPlainText -Force $mgPassword
      $mgCertificate = New-Object System.Security.Cryptography.X509Certificate2($mgCertPath, $mgPassword)

      Connect-MgGraph -Certificate $mgCertificate -TenantId $mgTenantId -ClientId $mgClientId

      Make sure you are downloading the secure file first in your pipeline job. All files are temporarily stored in the $env:AGENT_TEMPDIRECTORY location as far as I’m aware in Azure DevOps

      Something else I’ve noted, usually when using the PowerShell job in Pipelines, you would need to install the MgGraph cmdlets as part of the script. However, it appears that this is not needed.

      1. Steve Johnson

        Oops, missed a bit. $mgCertPath = “$env:AGENT_TEMPDIRECTORY/YourCertFile.pfx”

      2. Avatar photo
        Tony Redmond

        Thanks Steve… this is a bit of a journey for many people, so all advice is appreciated.

        1. Steve Johnson

          No problem at all, it’s all useful learning!

      3. Irwin Strachan

        Thanks for sharing! Never thought of using secure files… I needed to add X509Certificates just before X509Certificate2

        $mgCertificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($mgCertPath, $mgPassword)

        That did the trick!

        My alternative was to login using AccessToken. Just grab it

        $accessToken = Get-AzAccessToken -ResourceUrl “https://graph.microsoft.com/”
        Connect-MgGraph -AccessToken $accesstoken.Token

        Rg./Irwin

  15. TonyTCC

    Hi Tony,

    Is it possible connect the module with specific account?
    Something like below:

    Connect-MgGraph -Scopes “User.ReadWrite.All”,”Directory.ReadWrite.All” -Credential $Credential

    1. Avatar photo
      Tony Redmond

      The Graph SDK uses a service principal to connect and automatically picks up the credentials of the signed-in account for interactive sessions. For jobs run in the background, I think you’ll need to use a registered app, but I will ask…

      1. Avatar photo
        Tony Redmond

        I received confirmation that you can’t pass an account identifier to have the SDK use it. Sorry!

          1. Avatar photo
            Tony Redmond

            Azure Automation can definitely run Get-MgUser. You should be all set.

  16. Wouter

    Thank you for this

Leave a Reply