The Microsoft 365 provider can use a Service Principal with a Client Certificate to authenticate to Microsoft 365 services. This is a more secure authentication method compared to client secrets, as certificates are less susceptible to certain types of attacks and can be managed with stronger security controls.
Prerequisites
A Microsoft Entra ID tenant
Permissions to create an app registration in your tenant
Tools for generating certificates (OpenSSL, PowerShell, or other certificate management tools)
Generate a Certificate
Using OpenSSL
Using PowerShell
# Generate a private key
openssl genrsa -out key.pem 4096
# Generate a certificate signing request (CSR)
openssl req -new -key key.pem -out cert.csr
# Generate a self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in cert.csr -signkey key.pem -out cert.pem
# Create a PKCS#12 file that contains both certificate and private key
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -password pass:YourSecurePassword
# Create a self-signed certificate
$cert = New-SelfSignedCertificate - Subject "CN=Microsoft365TerraformAuth" `
- CertStoreLocation "Cert:\CurrentUser\My" `
- KeyExportPolicy Exportable `
- KeySpec Signature `
- KeyLength 2048 `
- KeyAlgorithm RSA `
- HashAlgorithm SHA256 `
- NotAfter ( Get-Date ).AddYears( 1 )
# Export the public certificate (for uploading to Azure)
Export-Certificate - Cert $cert - FilePath "cert.cer" - Type CERT
# Export the certificate with private key (for Terraform)
$pwd = ConvertTo-SecureString - String "YourSecurePassword" - Force - AsPlainText
Export-PfxCertificate - Cert $cert - FilePath "cert.pfx" - Password $pwd
Setup App Registration
Manual Setup
Using Terraform
Add API Permissions
Navigate to “API permissions”
Click “Add a permission” and select “Microsoft Graph”
Choose “Application permissions” for automation scenarios
Apply least privilege principles
Click “Grant admin consent”
Upload Certificate
Navigate to “Certificates & secrets” in your app registration
In the “Certificates” section, click “Upload certificate”
Upload your public certificate (.cer or .pem) file
Add a description and expiration date
# Generate a private key
resource "tls_private_key" "cert_key" {
algorithm = "RSA"
rsa_bits = 4096
}
# Generate a self-signed certificate
resource "tls_self_signed_cert" "cert" {
private_key_pem = tls_private_key . cert_key . private_key_pem
subject {
common_name = "Microsoft365TerraformAuth"
}
validity_period_hours = 8760 # 1 year
allowed_uses = [
"key_encipherment" ,
"digital_signature" ,
"client_auth" ,
]
}
# Create application
resource "azuread_application" "microsoft365_app" {
display_name = "Microsoft365 Terraform Provider"
}
# Add the certificate to the application
resource "azuread_application_certificate" "microsoft365_cert" {
application_id = azuread_application . microsoft365_app . id
type = "AsymmetricX509Cert"
value = tls_self_signed_cert . cert . cert_pem
end_date = timeadd ( timestamp (), "8760h" )
}
Provider Configuration
export M365_TENANT_ID = "00000000-0000-0000-0000-000000000000"
export M365_AUTH_METHOD = "client_certificate"
export M365_CLIENT_ID = "00000000-0000-0000-0000-000000000000"
export M365_CLIENT_CERTIFICATE_FILE_PATH = "/path/to/certificate.pfx"
export M365_CLIENT_CERTIFICATE_PASSWORD = "YourSecurePassword"
export M365_SEND_CERTIFICATE_CHAIN = "true" # Optional
provider "microsoft365" {
auth_method = "client_certificate"
}
provider "microsoft365" {
auth_method = "client_certificate"
tenant_id = "00000000-0000-0000-0000-000000000000"
entra_id_options = {
client_id = "00000000-0000-0000-0000-000000000000"
client_certificate = "/path/to/certificate.pfx"
client_certificate_password = "YourSecurePassword"
send_certificate_chain = true # Optional
}
}
Certificate Management
Certificate Rotation
Generate New Certificate
Create a new certificate following the generation methods above
Upload to App Registration
Upload the new certificate to your Entra ID app registration. Both old and new certificates can be active simultaneously.
Update Configuration
Update your Terraform configuration or environment variables to use the new certificate
Remove Old Certificate
After confirming the new certificate works, optionally remove the old certificate from the app registration
Set up a pipeline job to regularly scan for certificate expiration to ensure you have sufficient time to perform rotation.
Certificate Storage Options
Azure Key Vault Store certificates in Azure Key Vault and retrieve during Terraform execution
HashiCorp Vault Use Vault’s PKI engine for certificate management and rotation
Secure File Storage Ensure certificate files have restrictive permissions (chmod 600)
CI/CD Secrets Store certificates in your CI/CD pipeline’s secure secret storage
Using Azure Key Vault
# Retrieve certificate from Azure Key Vault
data "azurerm_key_vault" "example" {
name = "my-keyvault"
resource_group_name = "my-resource-group"
}
data "azurerm_key_vault_certificate" "example" {
name = "my-certificate"
key_vault_id = data . azurerm_key_vault . example . id
}
provider "microsoft365" {
auth_method = "client_certificate"
tenant_id = var . tenant_id
entra_id_options = {
client_id = var .client_id
client_certificate = data .azurerm_key_vault_certificate.example.certificate_data_base64
client_certificate_password = ""
}
}
HashiCorp Vault Integration
Store Certificates in Vault
# Store tenant_id and client_id
vault kv put secret/microsoft365/credentials \
tenant_id="00000000-0000-0000-0000-000000000000" \
client_id="00000000-0000-0000-0000-000000000000"
# Store certificate (base64 encoded)
vault kv put secret/microsoft365/certificate \
certificate="$( base64 -i /path/to/certificate.pfx)" \
password="YourSecurePassword"
Retrieve with Vault Provider
data "vault_kv_secret_v2" "microsoft365_creds" {
mount = "secret"
name = "microsoft365/credentials"
}
data "vault_kv_secret_v2" "microsoft365_cert" {
mount = "secret"
name = "microsoft365/certificate"
}
resource "local_file" "certificate" {
content_base64 = data . vault_kv_secret_v2 . microsoft365_cert . data [ "certificate" ]
filename = " ${ path . module } /temp_certificate.pfx"
file_permission = "0600"
}
provider "microsoft365" {
auth_method = "client_certificate"
tenant_id = data . vault_kv_secret_v2 . microsoft365_creds . data [ "tenant_id" ]
entra_id_options = {
client_id = data .vault_kv_secret_v2.microsoft365_creds. data [ "client_id" ]
client_certificate = local_file.certificate.filename
client_certificate_password = data .vault_kv_secret_v2.microsoft365_cert. data [ "password" ]
}
}
Security Considerations
Certificate Security Best Practices
Use strong passwords for PKCS#12 (.pfx) files
Protect private keys and certificates with proper file permissions
Never commit certificates or private keys to version control
Use certificates with reasonable expiration periods (typically 1-2 years)
Consider using HSMs for storing certificate private keys in high-security environments
For maximum security, use certificates from a trusted Certificate Authority rather than self-signed
Troubleshooting
Verify the tenant ID, client ID, and certificate details are correct. Ensure the certificate hasn’t expired.
Certificate not recognized
Ensure the certificate is properly formatted and uploaded to the app registration. The public key must be uploaded to Entra ID.
Certificate format issues
Ensure your certificate is in PKCS#12 format (.pfx or .p12). Convert if necessary using OpenSSL.
SendCertificateChain issues
If you’re having authentication problems, try setting send_certificate_chain to true.
Enable debug mode for detailed logging: export M365_DEBUG_MODE = "true"
Or in Terraform: provider "microsoft365" {
debug_mode = true
}