Create a service principal for automation — and rotate its credential safely

When no managed identity or federated credential fits, create a service principal that works on the first token request — app plus service principal (az ad sp create-for-rbac or az ad app create + az ad sp create), a certificate instead of a secret, a role at the narrowest scope, application permissions with admin consent for Microsoft Graph, and credential rotation with --append so the running workload never loses its credential.

markdownguidev1VJ76-MSHDstage: buildtrust: officialservice: entrainterface: cliinterface: sdkcreate service principalaz ad sp create-for-rbacaz ad app createaz ad sp createclient secretcertificate credentialaz ad app credential reset --appendsecret rotationcredential overwrittenAZURE_CLIENT_ID AZURE_TENANT_ID AZURE_CLIENT_SECRETClientCertificateCredentialapplication permissions admin consentaz ad app permission addaz ad app permission admin-consent--json-auth deprecated--sdk-auth deprecated

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

Create a service principal for automation

Use this only when the workload can't have a managed identity or a federated credential (reference/overview/services-and-identities.md). A service principal has a credential that can leak and expires; everything below keeps those two risks small.

1. Create the app and its service principal

The one-command way:

az ad sp create-for-rbac --name sp-nightly-report \
  --role Reader --scopes /subscriptions/<sub-id>/resourceGroups/rg-reports \
  --create-cert
  • No role is assigned unless you pass --role and --scopes. Without them, the principal exists but can do nothing.
  • --create-cert makes a self-signed certificate readable only by your OS user (add --keyvault <vault> --cert <name> to create it in Key Vault instead). Without a certificate option, the command creates a password (secret) valid for one year (--years to change it).
  • The output includes the credential once. Store it in a secret store at once; it can't be read back.
  • --json-auth / --sdk-auth is deprecated.

The two-step way, when you need control over the app:

az ad app create --display-name sp-nightly-report      # app object only
az ad sp create --id <appId>                            # service principal: required

Microsoft Graph, which the CLI uses, doesn't create the service principal for you; registering in the portal does. An app without one fails sign-in with AADSTS700016.

2. Prefer a certificate to a secret

Microsoft recommends a certificate from a trusted CA. With @azure/identity:

import { ClientCertificateCredential } from "@azure/identity";

// PEM file containing the certificate AND its private key. PFX isn't supported by the JS library.
export const credential = new ClientCertificateCredential(
  process.env.AZURE_TENANT_ID ?? "",
  process.env.AZURE_CLIENT_ID ?? "",
  { certificatePath: process.env.AZURE_CLIENT_CERTIFICATE_PATH ?? "" },
);

Through DefaultAzureCredential, set AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_CERTIFICATE_PATH. If AZURE_CLIENT_SECRET is also set, the secret wins.

3. Give it only what it needs

  • Azure resources: a role at the narrowest scope (guides/rbac/assign-roles.md). Data needs data roles; Contributor reads no secrets or blobs.
  • Microsoft Graph or another API, no user involved: add application permissions, then get admin consent. Without consent, Graph returns 403 Authorization_RequestDenied / Insufficient privileges to complete the operation.
az ad app permission add --id <appId> --api 00000003-0000-0000-c000-000000000000 --api-permissions <permission-id>=Role
az ad app permission admin-consent --id <appId>

Find <permission-id> with az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?value=='User.Read.All'].id" -o tsv (application permissions are the API's appRoles). 00000003-0000-0000-c000-000000000000 is Microsoft Graph's app ID; =Role marks an application permission (=Scope is delegated). The token then carries the permission in its roles claim, and requests use the scope https://graph.microsoft.com/.default.

4. Rotate without an outage

az ad app credential reset --id <appId> --append --display-name "2026-10 rotation" --years 1

Without --append, credential reset replaces the existing credentials: every running copy of the workload fails at its next token request with AADSTS7000215. The safe order is:

  1. Add the new credential with --append.
  2. Deploy it everywhere.
  3. Confirm sign-ins use it.
  4. Delete the old one: az ad app credential delete --id <appId> --key-id <old-keyId>. Find it with az ad app credential list --id <appId>, which shows each credential's keyId and endDateTime.

Put a reminder before endDateTime; an expired secret is AADSTS7000222.

Next

Token errors: reference/entra/errors.md. SDK errors: reference/azure-identity/errors.md.

Read this document as markdown — the same bytes an agent receives.