In the previous post on auditing MFA registration status in Entra ID, the final script used a service principal with certificate-based authentication to connect non-interactively to Microsoft Graph. This follow-up covers everything you need to do that setup from scratch: creating the app registration, generating a self-signed certificate, uploading the public key, assigning the correct Graph API permissions, and connecting with PowerShell. When done, your script will run fully unattended, with no user prompts and no stored passwords.

Why Certificate Authentication Instead of a Client Secret?

App registrations support two credential types: client secrets (a password) and certificates. Client secrets are convenient but have a fixed expiry and must be stored somewhere, which creates a secret management problem. Certificates use asymmetric cryptography: only the public key lives in Entra ID, while the private key stays on your machine or in a key vault. This makes them a better fit for automation scenarios and is the approach Microsoft recommends for production workloads.

Prerequisites

  • An Entra ID tenant where you have at least the Application Administrator role
  • PowerShell 7.2 or later (or Windows PowerShell 5.1) with the Microsoft.Graph module installed
  • A Global Administrator or Privileged Role Administrator available to grant admin consent on the API permissions

Step 1: Create the App Registration

Open the Entra admin center, go to Applications > App registrations, and click New registration. Give the app a descriptive name such as MFA-Audit-Automation, leave the supported account types set to Accounts in this organizational directory only, and click Register. Note the Application (client) ID and Directory (tenant) ID from the overview page, you will need both in your PowerShell script.

Step 2: Generate a Self-Signed Certificate

You can generate a certificate entirely in PowerShell. The snippet below creates a self-signed cert valid for two years, exports the public key as a .cer file for upload to Entra ID, and stores the full certificate (including private key) in the Windows certificate store so PowerShell can use it later by thumbprint:

# Generate a self-signed certificate in the current user store
$certParams = @{
    Subject           = 'CN=MFA-Audit-Automation'
    CertStoreLocation = 'Cert:\CurrentUser\My'
    KeyExportPolicy   = 'Exportable'
    KeySpec           = 'Signature'
    KeyLength         = 2048
    HashAlgorithm     = 'SHA256'
    NotAfter          = (Get-Date).AddYears(2)
}
$cert = New-SelfSignedCertificate @certParams

# Export the public key (.cer) for uploading to Entra ID
$cerPath = "$env:TEMP\MFA-Audit-Automation.cer"
Export-Certificate -Cert $cert -FilePath $cerPath -Type CERT | Out-Null

Write-Host "Certificate thumbprint: $($cert.Thumbprint)"
Write-Host "Public key exported to: $cerPath"

Keep a note of the thumbprint printed in the output. You will use it in your connection command instead of a password.

Step 3: Upload the Certificate to the App Registration

Back in the Entra admin center, open your new app registration and go to Certificates and secrets > Certificates. Click Upload certificate, browse to the .cer file you exported, add an optional description such as MFA Audit automation cert, and click Add. You will see the certificate thumbprint appear in the list once it is uploaded successfully.

You can also do this step entirely from PowerShell if you prefer to avoid the portal:

Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Read the public key bytes
$certBytes   = [System.IO.File]::ReadAllBytes($cerPath)
$certBase64  = [System.Convert]::ToBase64String($certBytes)

# Add the certificate credential to the app registration
$appId = "YOUR-APP-CLIENT-ID"
$app   = Get-MgApplication -Filter "appId eq '$appId'"

$keyCred = @{
    Type  = "AsymmetricX509Cert"
    Usage = "Verify"
    Key   = [System.Convert]::FromBase64String($certBase64)
}
Update-MgApplication -ApplicationId $app.Id -KeyCredentials @($keyCred)
Write-Host "Certificate uploaded to app registration."

Step 4: Assign Microsoft Graph API Permissions

The app needs application permissions (not delegated) because it will run without a signed-in user. In the Entra admin center, go to API permissions > Add a permission > Microsoft Graph > Application permissions and add the following:

  • User.Read.All – read user profiles
  • UserAuthenticationMethod.Read.All – read MFA registration details
  • AuditLog.Read.All – read sign-in and audit logs
  • Mail.Send – only required if your script sends summary emails via Graph

After adding the permissions, click Grant admin consent for [your tenant] and confirm. Application permissions always require admin consent because they apply tenant-wide without a user context. The status icons should turn green once consent is granted.

Step 5: Connect from PowerShell Using the Certificate

With the app registration configured and the certificate in your local cert store, connecting is a single command. Replace the three placeholder values with your own IDs and thumbprint:

$TenantId    = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ClientId    = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$CertThumb   = "YOUR-CERTIFICATE-THUMBPRINT"

Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $CertThumb

# Verify the connection
Get-MgContext | Select-Object Account, TenantId, AuthType, Scopes

The AuthType in the output should show AppOnly, confirming that the session is running as the service principal rather than as a user. You can now call any Graph cmdlet covered by the app permissions.

Step 6: Running on a Server or in a Scheduled Task

When running on a server or in a GitHub Actions workflow, the certificate private key needs to be accessible in that environment too. You have two main options:

  • Export to PFX and import on the target machine: export the cert including the private key to a .pfx file, copy it to the server, and import it into the machine certificate store.
  • Store in Azure Key Vault: for pipelines and cloud-hosted runners, store the PFX in Key Vault and retrieve it at runtime using the Az.KeyVault module.
# Export the certificate including private key (PFX)
$pfxPath     = "$env:TEMP\MFA-Audit-Automation.pfx"
$pfxPassword = ConvertTo-SecureString -String "UseAStrongPasswordHere" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath $pfxPath -Password $pfxPassword | Out-Null
Write-Host "PFX exported to: $pfxPath"

# On the target machine, import into the LocalMachine store for scheduled tasks
# (run this block on the destination server as a local admin)
$pfxPassword = ConvertTo-SecureString -String "UseAStrongPasswordHere" -Force -AsPlainText
Import-PfxCertificate -FilePath "C:\Certs\MFA-Audit-Automation.pfx" \
    -CertStoreLocation "Cert:\LocalMachine\My" \
    -Password $pfxPassword

Putting It All Together

Once the certificate is imported on your automation host, drop the three-line connection block (Step 5) at the top of any script and it will authenticate silently every time. For the MFA audit script from the previous post, replace the interactive Connect-MgGraph call with the certificate-based version, point a scheduled task at the script, and you have a fully unattended recurring compliance report with no passwords stored anywhere on disk.

As a general hygiene note, set a calendar reminder to renew the certificate before the two-year expiry. You can automate renewal too using the same PowerShell approach: generate a new cert, upload it to the app registration, update your scripts with the new thumbprint, and remove the old credential from the portal once you have confirmed the new one works.

Read also: Automating MFA Audits in Entra ID with PowerShell – how to use this service principal setup to run a full tenant-wide MFA registration report.