The Problem of Non-Interactive Jobs
The long-overdue move away from legacy authentication in some Microsoft 365 workloads introduces some problems for organizations when automating tasks. When code needs to run against tenant data as a background task, authenticating by using saved credentials is not possible without compromising on security.
Apps registered in Azure AD support the use of application permissions granted for the Microsoft Graph or other APIs. Application permissions use the service principal of a registered app to authenticate so don’t require a real user or service account to be passed when requesting an access token. This allows scripts (for example, reporting storage used by Teams channels) to be run non-interactively.
For more information on automating tasks in your environment, check out this article on Using Azure Automation to Process Exchange Online Data with PowerShell
How App Secrets Work
To use a registered app for non-interactive scripts, an authentication mechanism is needed to retrieve an access token from Azure AD. The easiest way to authenticate in an application context is with an app secret.
App secrets (sometimes referred to as client secrets) are randomly generated string values created by Azure AD. They can only be viewed as plain text at the time of creation and have a maximum lifetime of 24 months. App secrets are passed to Azure AD as part of an access token request along with the Client ID and Tenant ID of the app. If the request is validated successfully by Azure AD, an access token is returned with the response.
Generating an App Secret
An app secret is generated for a registered app by selecting the app and going to the Certificates & Secrets page. Select New client secret from the Client Secrets section and give it a name and expiration period – this generates a new secret value that can be used with the registered app. The secret value is only visible immediately after creation, and navigating away from the page will cause the app secret to become obfuscated as shown in Figure 1:
The app secret can be used in a token request as shown below:
#Declare Variables $clientId = "b74b5466-0000-4b2f-bdc2-100033b00436" $tenantId = "c6c74377-0000-4abb-84c2-81ddce1f75ac" $clientSecret = "pDL7Q~MbgWVk2FmxTb6WiubC.jA06zcK0000" # Construct URI $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" # Construct Body $body = @{ client_id = $clientId scope = "https://graph.microsoft.com/.default" client_secret = $clientSecret grant_type = "client_credentials" } # Get OAuth 2.0 Token $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
The Problems with App Secrets in Production
App secrets are easy to create and use, therefore they are particularly useful during testing and development. For instance, many of the articles I’ve written explaining how to use the Microsoft Graph APIs to interact with Exchange Online (like this example) use app secrets to connect. The problem with using secrets in production is that there’s always a risk the secret will become compromised.
Because secrets are simple string values, they are often stored in config files, hardcoded in scripts, or simply saved by an admin. Once the secret is compromised, any permissions granted to the service principal can be used freely by anyone who knows the Tenant and Client IDs. Worse still, because the service principle is not tied to any one user, it’s impossible to tell who connected or made changes. The most information that can be retrieved after the fact is the source IP, available from the service principal sign-in logs in Azure AD (Figure 2):
What alternatives are available?
When automating tasks in production, certificate authentication is supported for service principal sign-in. A self-signed X.509 certificate can be created and uploaded to the app registrations Certificates page. The certificate, stored on the machine you run the automation from, is used in place of the secret when requesting a token.
Requesting a token is more difficult when using certificates but the MSAL.PS PowerShell module does most of the heavy lifting for you, so I highly recommend using it. The example below imports a certificate from the local user certificate store and requests an access token using the MSAL module:
Import-Module MSAL.PS ##Declare Variables $ClientID = "065297f4-0000-409e-a25d-2f97eb2a34ce" $TenantID = "209f39e2-0000-4bfc-aea5-85cfe2a22e39" $CertificatePath = "cert:\currentuser\my\6C1EE1A11F57F2495B57A567211220E0ADD72DC1" ##Import Certificate $Certificate = Get-Item $certificatePath ##Request Token $Token = Get-MsalToken -ClientId $ClientId -TenantId $TenantId -ClientCertificate $Certificate
Azure AD also supports the use of federated credentials for application authentication through an external OIDC identity provider. The use cases here are limited but authentication via an external IdP is possible using a trust relationship.
Additional Methods to Secure App Authentication
Certificates are an improvement over app secrets, but there is no perfect way to secure app authentication. However, there are other steps that admins can take to improve the security around the process.
For instance, organizations with Azure AD Premium P2 licenses can leverage the (preview) feature to protect workload identities with conditional access. This allows conditional access policies to target the service principal identity. Currently, this feature only supports blocking access based on location / IP address but a policy to lock down service principal sign-in to a specific public IP (Figure 3) greatly reduces risk.
Another method for enhancing app authentication security is to leverage Azure Automation and Azure Key Vault for automation tasks. This provides some strong protection around the management of credentials or certificates with tight controls for access.
Summary
App secrets can be great for testing code, but there’s a reason that they have an enforced expiry date. The longer a secret exists in production, the higher the risk it will become compromised.
As many organizations adapt legacy scripts to use app authentication instead of traditional service account credentials, it’s easy to miss the risks associated with app secrets. The methods described here help build a good foundation for app authentication and keep security top of mind when creating or updating automation scripts.
Hello,
For an organization using Azure CAP to prevent users from logging in outside the corporate network, if a user has the Application ID, Tenant ID, and Secret in hand, they will be able to get a token and authenticate outside the corporate network using the service principal (i.g. via PowerShell) which makes it even more risky. The only way to reduce the risk is to configure a CAP and block the application from unwanted IP addresses.On the other hand, this could cause the action you are trying to perform to be blocked, as it could communicate with another Microsoft service on a different IP address that is not yet registered in your known location.
I think the explanation lacks of details, why secrets are less secure than certificates. Certificates are also *one* credential used for authentication. If several people share the certificate, you still don’t know who actually used it. Certificates also have an expiration date.
But: it’s way more difficult to brute force a certificate, than a secret string. And it’s a bit more difficult to share among other admins in a company.
Hi Andreas,
I agree Certificates are not perfect but are definitely an improvement over secrets (more difficult to brute force, easier to control access vs a simple sting) .
The additional functionality provided by Conditional Access Workload Identities should help here. As stated, there’s no perfect fix, but there are measures you can take to reduce risk.