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).
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).
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.
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?
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?
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)
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.
Thank you for this! Worked perfectly. My cert expired and I couldn’t remember how the heck I did it a year ago.
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!
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.
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
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—–‘
Not being a Python expert, I hope this helps: https://blog.darrenjrobinson.com/microsoft-graph-using-msal-with-python-and-certificate-authentication/
This might also help: https://blogs.aaddevsup.xyz/2023/03/using-msal-for-python-to-perform-interactive-sign-in-from-a-local-script/
anyway on doing this on a mac using Powershell Graph SDK
You could store the certificate thumbprint in Azure Key Vault and fetch it from there when you need to run the script.
did you manage to get this to work?
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?
Works like a charme! Thanks!