Moving Away from Interactive Sessions

In the articles like updating licenses for Azure AD accounts which use the Microsoft Graph PowerShell SDK to access Microsoft 365 data, we’ve used command issued in interactive sessions. As I point out in this article, this approach is great for exploring how the Graph SDK cmdlets work, while also having some significant points for programmers and administrators to understand. The most serious issue is permission accrual where consents gathered over time mean that the service principal used for the SDK can become over-permissioned. The solution is to use separate apps registered in Azure AD with consent for just enough permissions to get a job done; this creates a management challenge but it’s much more secure.

If you go along the path of using registered apps, you can use app-only authentication instead of delegated access. App-only authentication means that the app uses an X.509 certificate as its credential. The certificate can be self-signed (for testing purposes) or issued by a certificate authority. Microsoft’s documentation for using app-only authentication for the Microsoft Graph PowerShell SDK contains the steps to configure an app registered in Azure AD for app-only authentication. This article covers my experience of using the steps.

Get a Certificate

You probably don’t have an X.509 certificate hanging around waiting to be used, but they are easy to create with PowerShell. This code example runs the New-SelfSignedCertificate cmdlet to create a new certificate stored in the current user’s local certificate store on the workstation where the command runs:

$Cert = New-SelfSignedCertificate -DnsName office365itpros.onmicrosoft,com -CertStoreLocation "Cert:\CurrentUser\My" -FriendlyName "MicrosoftGraphSDK" -Subject "Test Cert for Microsoft Graph SDK"
Get-ChildItem "Cert:\CurrentUser\My\$($Cert.thumbprint)"

PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
FC92991B21219F178AFB37C12DF231B6AFC3D790  CN=office365itpros.onmicrosoft

If you want to create the certificate in the certificate store for the local machine, specify Cert:\LocalMachine\My as the store location. You can only create new certificates in the local machine store if you run PowerShell as an administrator. For testing purposes, it’s OK to store the certificates you use in your store. When the time comes to put something into production, you’ll need different certificates installed on the machines where the code will run.

After creating the certificate, we export it to a .cer file. Here’s what I did:

Get-ChildItem "Cert:\CurrentUser\My\$($Cert.thumbprint)" | Export-Certificate -FilePath C:\temp\MicrosoftGraphSDK.cer

    Directory: C:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        06/10/2021     17:01            861 MicrosoftGraphSDK.cer

Connecting the Certificate to an App

Azure AD registered apps act as containers to hold consent for a set of Graph permissions. The permissions enable the app to access data using Graph queries. When a script connects using app-only authentication, it authenticates by passing the thumbprint of a certificate known to the app instead of another mechanism like an interactive password or an app secret.

To make this possible, we must load the certificate file created using the Export-Certificate cmdlet into the app, to be able to complete the certificate-based authentication process. This is done by going to the certificates and secrets section of the app properties and choosing the upload certificate option (Figure 1).

Uploading a certificate file to an Azure AD app
Figure 1: Uploading a certificate file to an Azure AD app

Remembering that the app holds the permissions we want to use with Graph queries, we need to add the required Graph permissions to the app. The exact permissions vary according to the data the app wants to access. You can experiment by running code in an interactive session to figure out the right set of permissions and then add them to the app. In Figure 2, I’ve added five application permissions ranging from Auditlog.Read.All (needed to read user sign in data) to User.Read.All (needed to read information about user accounts).

Graph permissions configured for an Azure AD app
Figure 2: Graph permissions configured for an Azure AD app

Note that the delegated User.Read permission is removed. This is a default permission which we don’t need.

Connecting to the Microsoft Graph

We need three pieces of information to connect to the Graph using the Connect-MgGraph cmdlet:

  • The tenant identifier. You can copy this from the app properties.
  • The app identifier. Again, this is available in the app properties.
  • The certificate thumbprint. This is available as a property of the certificate (see the example above).

With this information, we can run a command like (remember to replace the tenant identifier, app identifier, and certificate thumbprint values as appropriate for your tenant):

Connect-MgGraph -TenantId "a562313f-14fc-43a2-9a7a-d2e27f4f3478" -AppId "d86b1929-b818-411b-834a-206385bf5347" -CertificateThumbprint "FC92991B21219F178AFB37C12DF231B6AFC3D790"

Welcome To Microsoft Graph!

To check that you’ve connected with the correct settings, run the Get-MgContext cmdlet. The important things to check here are the input settings for client identifier, tenant identifier, and thumbprint plus the permissions (scopes) available. The context scope should report Process instead of CurrentUser (an interactive login) and there should be no account name shown.

Get-MgContext

ClientId              : d86b1929-b818-411b-834a-206385bf5347
TenantId              : a562313f-14fc-43a2-9a7a-d2e27f4f3478
CertificateThumbprint : FC92991B21219F178AFB37C12DF231B6AFC3D790
Scopes                : {Group.Read.All, MailboxSettings.Read, User.Read.All, Organization.ReadWrite.All...}
AuthType              : AppOnly
AuthProviderType      : ClientCredentialProvider
CertificateName       :
Account               :
AppName               : Microsoft Graph PowerShell SDK Cert
ContextScope          : Process
Certificate           :

If something doesn’t look right, run the Disconnect-MgGraph cmdlet to terminate the session, go back and check the app settings (in particular, make sure that consent is given for all the required permissions), and try again.

Running Code

Once you’ve connected successfully with the right permissions, you can run your code. As usual, interactive prompts won’t work, so some adjustments might be necessary to take code created for an interactive session and make it work in a background job. After that, it’s a matter of figuring out operational details like:

  • The X.509 certificates to use for authentication and how to install them in the right place
  • How to submit the script as a background the job.
  • Deal with the output (for instance, emailing a file with the script’s results).

All of which is another day’s work.

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. Jonathan

    Accidentally replied to another post rather than the article before.

    I set this up and it works great when the service account that created the cert is executing the script or when that same account runs it as a task. However, authentication fails due to invalid or expired cert if I try to run the script from my own user. Do you know why this might be?

    1. Avatar photo
      Tony Redmond

      The script must be able to read the certificate from Entra ID… Maybe your account doesn’t have the permission/role to interact with Entra?

  2. Mp

    There is no way to do delegated auth in this fashion correct? For background processes where mfa isn’t an option and you need delegated rights you are stuck with user/pass (ropc)

    1. Carter Griffin

      unfortunately that’s correct. if the permission isn’t available for applications it will need to be interactive. this is frustrating – prevented me from updating files programmatically on a single sharepoint site without giving the app access to ALL sharepoint sites. fingers crossed graph matures soon.

  3. Wes

    Thank you for this! Worked perfectly. My cert expired and I couldn’t remember how the heck I did it a year ago.

  4. Kenneth

    Dear Tony,

    Has anything changed in the way this works?
    I am running following code:

    Connect-MgGraph -TenantId $tenant -AppId $application -CertificateThumbprint $certThumb

    But powershell keeps throwing an error, and I don’t find any info on how to troubleshoot this.

    Connect-MgGraph : Parameter set cannot be resolved using the specified named parameters.
    + CategoryInfo : InvalidArgument: (:) [Connect-MgGraph], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.Graph.PowerShell.Authentication.Cmdlets.ConnectMgGraph

    Hope you have some time to help!

    1. Avatar photo
      Tony Redmond

      Works for me:

      Connect-Mggraph -NoWelcome -AppId $AppId -TenantId $TenantId -CertificateThumbprint $CertThumbprint

      et-mgcontext

      ClientId : 8f005189-8c58-4fb5-a226-8851e13490cb
      TenantId : a662313f-14fc-43a2-9a7a-d2e27f4f3478
      Scopes : {CrossTenantInformation.ReadBasic.All, Directory.Read.All, User.Read.All, Chat.Read.All…}
      AuthType : AppOnly
      TokenCredentialType : ClientCertificate
      CertificateThumbprint : F79286DB88C21491110109A0222348FACF694CBD
      CertificateSubjectName :
      Account :
      AppName : PowerShellGraph
      ContextScope : Process
      Certificate :
      PSHostVersion : 7.4.0
      ManagedIdentityId :
      ClientSecret :
      Environment : Global

      I’m using V2.9.1 of the SDK.

  5. Doe Joy

    That’s great article. But whenever trying to connect with certificate (.pfx) file always getting timeout issue. I have firewall enable because it is working with secret. I am owing a website https://JTSofttech.com

  6. Davide

    That works amazing, thank you! But, how do we authenticate via api.GraphAPI.from_certificate() in python?
    Looks like it requires way more data, like: authority_host_uri, tenant, resource_uri, client_id, client_thumbprint, client_certificate)
    File “site-packages\adal\self_signed_jwt.py”, line 55, in _sign_jwt
    raise AdalError(“Error:Invalid Certificate: Expected Start of Certificate to be ‘—–BEGIN RSA PRIVATE KEY—–‘”, exp)adal.adal_error.AdalError: Error:Invalid Certificate: Expected Start of Certificate to be ‘—–BEGIN RSA PRIVATE KEY—–‘

  7. Curibe

    anyway on doing this on a mac using Powershell Graph SDK

    1. Avatar photo
      Tony Redmond

      You could store the certificate thumbprint in Azure Key Vault and fetch it from there when you need to run the script.

    2. ekow

      did you manage to get this to work?

  8. pg

    Thanks for sharing! when using Connect-MgGraph command, the function app console return command not found error. Is there a way to specify the module version in requirements.psd1 file or the module needs to be manually downloaded and uploaded?

  9. Mike

    Works like a charme! Thanks!

Leave a Reply