Azure Service Principals: App Identities for Automation and CI/CD

When an application or script needs to authenticate to Azure and perform actions on your behalf, it needs an identity. Azure service principals are that identity — they represent applications in Microsoft Entra ID and can be granted permissions to Azure resources just like a human user can.

What a service principal is

Every time a human logs into Azure, they authenticate as a user object in Microsoft Entra ID. But what about a deployment script, a CI/CD pipeline, or a monitoring tool? These workloads also need to authenticate, but they can’t use a human’s credentials — there’s no one to type a password or approve an MFA prompt.

A service principal is an identity in Entra ID designed for exactly this use case. It is a non-human identity that an application uses to authenticate to Azure. Like a user, a service principal can be assigned RBAC roles (Reader, Contributor, Owner, or custom roles) on subscriptions, resource groups, or individual resources.

Service principals are created as part of an app registration. When you register an app in Entra ID, two objects are created:

  • The application object — the global definition of the app, stored in the home tenant where it was created
  • The service principal object — the local instance of the app in each tenant where it’s used

For most Azure automation scenarios, you work with the service principal directly rather than the application object.

Creating a service principal

The simplest way to create a service principal with Azure CLI is the az ad sp create-for-rbac command, which creates the app registration, the service principal, and assigns an RBAC role in one step:

# Create a service principal with Contributor role on a specific resource group
az ad sp create-for-rbac \
  --name "sp-github-actions-deploy" \
  --role "Contributor" \
  --scopes "/subscriptions/{sub-id}/resourceGroups/rg-webapp-prod" \
  --json-auth

This outputs a JSON block containing four values you will need:

{
  "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "clientSecret": "your-generated-secret",
  "subscriptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
Danger

The client secret is displayed exactly once. Copy it immediately and store it in a secrets manager (Azure Key Vault, GitHub Secrets, or similar). If you lose it, you must generate a new secret — the original cannot be retrieved. Never log, commit to git, or print client secrets in CI/CD output.

To list existing service principals in your tenant:

# List service principals (shows display name, app ID, and type)
az ad sp list --filter "displayName eq 'sp-github-actions-deploy'" --output table

Credential types: secrets vs certificates

Service principals support two types of credentials for authentication:

Client secrets

A client secret is a password-like string. It has an expiration date (max 2 years) and must be rotated before it expires, or the service principal will stop authenticating. Secrets are easy to set up but require a process for rotation — if a secret expires in a production pipeline and no one notices, deployments start failing at midnight.

Certificate-based authentication

A certificate is more secure than a secret because the private key never leaves the system that holds it. You generate a certificate, upload the public certificate to the service principal, and the application uses the private key to sign authentication requests. Certificates are preferred for production workloads where a secret rotation process is hard to maintain reliably.

# Create a self-signed certificate and configure the service principal to use it
# (Generate the certificate locally first)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

# Upload the certificate to the service principal
az ad sp credential reset \
  --id "sp-github-actions-deploy" \
  --cert @cert.pem
Tip

For new workloads hosted inside Azure, skip the secret vs. certificate debate entirely and use a managed identity instead. Azure rotates the credentials automatically, and there’s nothing to store. See users vs managed identities for a full comparison.

Real scenario: GitHub Actions deploying to Azure

GitHub Actions is a common place where service principals are necessary. GitHub’s runners are not Azure resources, so managed identities don’t apply. Here is the setup:

Step 1: Create the service principal

az ad sp create-for-rbac \
  --name "sp-github-deploy-webapp" \
  --role "Contributor" \
  --scopes "/subscriptions/{sub-id}/resourceGroups/rg-webapp-prod" \
  --json-auth

Step 2: Store the output in GitHub Secrets

In your GitHub repository, go to Settings → Secrets and variables → Actions → New repository secret. Name it AZURE_CREDENTIALS and paste the full JSON output from step 1.

Step 3: Use it in your workflow

name: Deploy to Azure App Service

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Azure
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy to App Service
        uses: azure/webapps-deploy@v2
        with:
          app-name: acme-web-app
          package: .

The azure/login action reads the JSON secret, extracts the client ID, client secret, tenant ID, and subscription ID, and authenticates the runner as the service principal. Every subsequent az command in the workflow runs with the permissions granted to that service principal.

Why not use a user account for this?

Using a personal user account in CI/CD is a common mistake. Problems with that approach: the pipeline breaks when the user leaves the company, MFA prompts prevent automated login, and the account has broader permissions than the pipeline needs. A service principal scoped to exactly the resource group it needs to deploy to is safer and more maintainable.

Note

GitHub Actions also supports federated credentials (OIDC), which lets GitHub authenticate to Azure without storing any secret at all. The GitHub workflow requests a short-lived token from GitHub’s OIDC provider, and Azure’s Entra ID trusts it. This is the most secure option and eliminates the secret rotation problem entirely. It’s worth investigating once you’re comfortable with the basic service principal setup.

Service principals vs managed identities

Service principals and managed identities both represent non-human identities in Entra ID. The difference is who manages the credentials:

AspectService PrincipalManaged Identity
Credential managementYou manage — must rotate secrets/certs before expiryAzure manages automatically
Where it can be usedAnywhere — local tools, CI/CD runners, on-premises servers, third-party servicesOnly on Azure resources that support managed identities (VMs, Functions, AKS, etc.)
Secret storage requiredYes — client secret or certificate must be stored somewhereNo — no credentials to store or rotate
Setup complexityModerate — create SP, assign role, store credential, handle rotationLow — enable on the Azure resource, assign RBAC role, done
Typical use caseGitHub Actions, Terraform local runs, Jenkins, third-party SaaS toolsAzure VM connecting to Key Vault, Azure Function reading from Storage, AKS pod accessing SQL Database

The rule of thumb: if the workload runs inside Azure, use a managed identity. If the workload runs outside Azure, use a service principal. For a deeper discussion, see users vs managed identities and managed identities overview.

Granting and reviewing service principal permissions

Service principals receive permissions through RBAC role assignments, exactly like users. You can scope permissions to a subscription, resource group, or individual resource.

# Assign a role to a service principal on a resource group
az role assignment create \
  --assignee "sp-app-id-here" \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/{sub-id}/resourceGroups/rg-webapp-prod"

# List all role assignments for a specific service principal
az role assignment list \
  --assignee "sp-app-id-here" \
  --all \
  --output table

# Remove a role assignment (least privilege — remove what you no longer need)
az role assignment delete \
  --assignee "sp-app-id-here" \
  --role "Contributor" \
  --scope "/subscriptions/{sub-id}/resourceGroups/rg-webapp-prod"

Grant only the permissions the service principal actually needs — not Contributor across the whole subscription when it only needs to write to one storage account. Over-privileged service principals are a common attack vector: if the credentials are compromised, a narrow-scoped principal limits the blast radius. See Azure RBAC for the built-in roles available.

Common service principal mistakes

  1. Creating one service principal for everything. A single “super SP” with Contributor access to the whole subscription is convenient but dangerous. If the secret leaks, the attacker has full access. Create one service principal per workload, scoped to exactly what that workload needs.
  2. Not tracking secret expiry dates. Client secrets expire (max 2 years). If nothing sends an alert before expiry, the pipeline fails suddenly at 2 AM. Use Azure Key Vault to store secrets and set expiry notifications, or switch to certificate authentication with a managed rotation process.
  3. Using a service principal for an Azure-hosted workload when a managed identity would work. Service principals require credential management. Managed identities don’t. If your Azure Function or VM is authenticating to Azure Key Vault with a service principal, there’s a simpler path available.
  4. Deleting the app registration without removing the role assignments first. When you delete an app registration, role assignments for the associated service principal become “stale” — they reference an object that no longer exists. These orphaned assignments show up in RBAC views as unknown principals and should be cleaned up manually.

Frequently asked questions

What is an Azure service principal?

A service principal is a non-human identity created in Microsoft Entra ID that represents an application or automation tool. It has credentials (a client secret or certificate) and can be granted RBAC roles on Azure resources, just like a user can. Unlike a user account, a service principal has no email address and cannot log into the Azure portal interactively.

When should I use a service principal instead of a managed identity?

Use a service principal when the workload runs outside Azure — for example, a GitHub Actions workflow, a Jenkins server on-premises, or a third-party tool like Terraform running on a developer laptop. Use a managed identity when the workload runs inside Azure (an Azure VM, an Azure Function, an AKS pod). Managed identities are simpler because Azure handles credential rotation automatically and there is no secret to store.

Is a service principal the same as an app registration?

Related, but not the same. An app registration is the global definition of an application in Entra ID — it lives in the directory that owns the app. A service principal is the local instance of that app in a specific Entra ID tenant. When you register an app in your tenant and want to grant it access to Azure resources, the service principal is the object that receives the RBAC role assignment.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.