Identity and Access
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Commands and behaviour below are current for Azure CLI 2.90.0 (checked 2026-09-26); re-verify against
az version and the linked reference pages before relying on them in a pipeline.
Every Bookshelf resource (rg-bookshelf, kv-bookshelf, stbookshelf, acrbookshelf, and so on — see
Azure) is reached by something that first has to prove who it is and then be told what
it may do. This page is that "who" and "what": Microsoft Entra ID as the identity provider, Azure RBAC as the
authorization model layered on top of it, and managed identities plus DefaultAzureCredential as the mechanism
that lets Bookshelf’s own services (catalog-api, orders-api, notifier) authenticate without ever holding a
secret.
Microsoft Entra ID: the identity layer
Microsoft Entra ID is Azure’s cloud identity provider: it holds users, groups, and the identities that
represent applications. Older material — including both books this section drew on for scope — calls this
service "Azure Active Directory"; that name was retired and the current name is Microsoft Entra ID. The
underlying tenant, object model and most az ad command names are unchanged, only the product name moved.
Three related-but-distinct objects represent an application in Entra ID:
- App registration
-
The template for an application: a global definition (client ID, redirect URIs, requested API permissions, certificates/secrets, federated credentials) created once, typically in the tenant that owns the application’s source code.
- Service principal
-
The local, usable identity created from an app registration (or a managed identity, see below) inside a specific tenant. Azure RBAC role assignments and Conditional Access policies target the service principal, never the app registration directly — an app registration has no permissions of its own until a service principal for it exists in the tenant where access is needed.
- Enterprise application
-
The name the Azure portal gives to a service principal’s entry in Enterprise applications, whether that service principal backs an app registration owned in this tenant, a multi-tenant SaaS application a user consented to, or a managed identity. In other words, "enterprise application" and "service principal" are the same object viewed from two portal blades.
Bookshelf uses this pattern directly: the app registration bookshelf-github-actions-deploy (covered under
workload identity federation below) has one service principal, which is what az role assignment create and
Conditional Access actually reference.
Authentication features (brief)
- Single sign-on (SSO)
-
One Entra ID sign-in covers every connected application (Microsoft 365, custom apps using OIDC/SAML/WS-Fed), so users authenticate once per session instead of per application.
- Multi-factor authentication (MFA)
-
A second proof of identity (an authenticator app, a hardware key, a phone prompt) in addition to a password, enforced tenant-wide or per Conditional Access policy.
- Passwordless authentication
-
Sign-in with no password at all — a passkey/FIDO2 security key, Windows Hello for Business, or the Microsoft Authenticator app’s phone sign-in — which removes password phishing as an attack vector entirely rather than just adding a second factor to it.
- Conditional Access
-
If-this-then-that access policies evaluated at every sign-in ("if the user is in the Finance group and the sign-in is from an unmanaged device, require MFA and block downloads"), driven by signals such as group membership, device compliance, location and sign-in risk.
- Zero Trust and defence-in-depth
-
Zero Trust is the operating principle behind all of the above: never trust a request by network location alone, verify every identity, device and session explicitly, and grant only the access a task needs. Defence-in-depth is the resulting architecture — Entra ID authentication, Conditional Access, Azure RBAC scoped to the narrowest resource, network controls and resource-level firewalls each act as an independent layer, so a failure in one does not expose Bookshelf’s data on its own.
Azure RBAC: who can do what, where
Azure role-based access control answers one question for every management-plane or data-plane call: does this principal have this role’s permissions at this scope. A role assignment is always the combination of the three:
-
Principal — a user, group, service principal, or managed identity.
-
Role definition — a named set of permitted (and, for a custom role, explicitly denied) operations, either built-in (
Contributor,Reader,Owner,Key Vault Secrets User, …) or custom. -
Scope — a management group, subscription, resource group, or a single resource, with permissions inherited downward.
Control plane vs. data plane roles
Azure separates managing a resource from using it, and assigns roles for each separately:
-
Control-plane (Azure Resource Manager) roles govern the resource’s own lifecycle — creating, deleting, configuring
kv-bookshelfitself.Owner,ContributorandReaderare the built-in examples. -
Data-plane roles govern the data or operations inside a resource once it exists — reading a secret from
kv-bookshelf, writing a blob tostbookshelf.Key Vault Secrets UserandStorage Blob Data Contributorare examples.
The reason they are assigned separately, rather than folded into one role, is that Contributor on a Key Vault
grants full control over the vault’s configuration, network rules and access policies — which is enough to
list which secrets exist and reconfigure who can read them — but grants no permission to read a secret’s
value. Conversely, Key Vault Secrets User can read every secret’s value but cannot change the vault’s
firewall or delete it. catalog-api’s managed identity gets only `Key Vault Secrets User on kv-bookshelf and
only Storage Blob Data Contributor on stbookshelf; nothing in Bookshelf’s deployment pipeline needs
control-plane rights on either resource at runtime.
In the Azure portal
-
Open the target resource (or resource group/subscription for a broader scope) and select Access control (IAM).
-
Select Add → Add role assignment.
-
On the Role tab, search for the role (
Key Vault Secrets User,Storage Blob Data Contributor,Contributor, or a custom role) and select it. -
On the Members tab, choose Managed identity, User, group, or service principal as appropriate, and pick the specific principal (e.g. the
id-catalog-apiuser-assigned identity used in the CLI example below). -
Review and select Review + assign.
-
To inspect existing assignments, return to Access control (IAM) → Role assignments and filter by scope or principal.
With the Azure CLI
# Data-plane: let catalog-api's managed identity read secrets from kv-bookshelf
az role assignment create \
--assignee-object-id "$(az identity show --name id-catalog-api --resource-group rg-bookshelf --query principalId -o tsv)" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "$(az keyvault show --name kv-bookshelf --query id -o tsv)"
# Data-plane: same identity, blob access on stbookshelf
az role assignment create \
--assignee-object-id "$(az identity show --name id-catalog-api --resource-group rg-bookshelf --query principalId -o tsv)" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "$(az storage account show --name stbookshelf --resource-group rg-bookshelf --query id -o tsv)"
# List everything assigned at the resource group scope
az role assignment list --resource-group rg-bookshelf --output table
Derived from Assign Azure roles using the Azure CLI.
A custom role is a JSON role definition with Actions (control-plane, ARM operations), DataActions
(data-plane operations), and AssignableScopes:
{
"Name": "Bookshelf Secret Reader (custom)",
"Description": "Read-only access to secrets in kv-bookshelf; no configuration changes.",
"Actions": [],
"NotActions": [],
"DataActions": [
"Microsoft.KeyVault/vaults/secrets/getSecret/action"
],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/<subscription-id>/resourceGroups/rg-bookshelf"
]
}
az role definition create --role-definition custom-secret-reader.json
Derived from Create or update Azure custom roles using Azure CLI.
Managed identities
A managed identity is a service principal that Entra ID creates and rotates on behalf of an Azure resource, so that resource can authenticate to other Azure services without any secret ever appearing in code, configuration, or an environment variable. There are two kinds:
- System-assigned
-
Tied to the lifecycle of one resource (e.g. `catalog-api’s Container App): created when the feature is enabled on that resource and deleted automatically when the resource is deleted. It cannot be shared with another resource.
- User-assigned
-
A standalone Entra ID object created independently of any resource, which can then be attached to one or many resources (e.g. the same
id-notifieridentity attached to both thenotifierjob and a backup Function App). It outlives any single resource and is the better fit when several resources need the same permission set, or when an identity must exist before the resource that will use it.
Once a managed identity exists, a resource "picks it up" by having it attached at the resource level (system-
assigned: a toggle on the resource; user-assigned: the identity’s resource ID added to the resource’s identity
list). The platform then makes a local token endpoint available to that resource’s runtime (the Instance Metadata
Service for VMs and AKS, or an equivalent sidecar/environment binding for Container Apps and App Service), and any
SDK using DefaultAzureCredential (below) picks up a token from it automatically — there is no key to
provision, rotate, or leak, which is exactly why managed identities remove the need for credentials in
application code.
Bookshelf uses a user-assigned identity, id-catalog-api, for the catalog-api Container App, so the identity
(and its role assignments above) can exist before the app is deployed and survive a redeploy. Because the
Azure Identity libraries only pick a user-assigned identity when told which one, the app also needs the
identity’s client ID — set as the AZURE_CLIENT_ID environment variable on the Container App, which
DefaultAzureCredential reads.
In the Azure portal
-
Search for Managed Identities and select + Create.
-
Choose the subscription, resource group
rg-bookshelf, the region, and enterid-catalog-apias the name. Select Review + create, then Create. -
Open the
catalog-apiContainer App and, in the Settings group, select Identity. -
On the User assigned tab, select Add, search for and select
id-catalog-api, then select Add. -
For a system-assigned identity instead, use the System assigned tab on the same Identity page: switch Status to On and select Save.
With the Azure CLI
# Create the user-assigned identity
az identity create \
--name id-catalog-api \
--resource-group rg-bookshelf \
--location eastus2
# Attach it to the catalog-api Container App
az containerapp identity assign \
--name catalog-api \
--resource-group rg-bookshelf \
--user-assigned "$(az identity show --name id-catalog-api --resource-group rg-bookshelf --query id -o tsv)"
# Tell DefaultAzureCredential which user-assigned identity to use
az containerapp update \
--name catalog-api \
--resource-group rg-bookshelf \
--set-env-vars AZURE_CLIENT_ID="$(az identity show --name id-catalog-api --resource-group rg-bookshelf --query clientId -o tsv)"
# Alternative: a system-assigned identity, created and deleted with the app itself
az containerapp identity assign --name catalog-api --resource-group rg-bookshelf --system-assigned
# Inspect what is attached
az containerapp identity show --name catalog-api --resource-group rg-bookshelf
Derived from az identity create and az containerapp identity assign.
DefaultAzureCredential and the credential chain
DefaultAzureCredential, from the azure-identity library (used directly, or via Spring Cloud Azure’s Spring
Boot starter, on the Java side), tries a sequence of credential sources in order and uses the first one that
succeeds — environment variables, a workload identity, a managed identity, then developer tooling such as the
Azure CLI’s own logged-in session — so the same code authenticates unmodified whether it runs on a developer’s
machine, in CI, or as a deployed Container App with a managed identity attached. The exact order, and how to
narrow it, is documented per language on the reference pages linked below.
Trying every source in turn costs a little latency and can pick up the wrong credential source in an environment
that has more than one available (a developer machine that is both az login-authenticated and has a stale
environment-variable credential set, for instance). The AZURE_TOKEN_CREDENTIALS environment variable pins the
chain to a named subset — for example, AZURE_TOKEN_CREDENTIALS=ManagedIdentityCredential in a Container App
forces exactly that source and fails fast instead of silently falling through to something else.
Java (Spring Boot / Spring Cloud Azure)
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
SecretClient secretClient = new SecretClientBuilder()
.vaultUrl("https://kv-bookshelf.vault.azure.net/")
.credential(credential)
.buildClient();
String connectionString = secretClient.getSecret("orders-db-connection-string").getValue();
With the Spring Cloud Azure starter (spring-cloud-azure-starter-keyvault), the same credential chain is wired
through configuration instead of code:
spring:
cloud:
azure:
keyvault:
secret:
endpoint: https://kv-bookshelf.vault.azure.net/
credential:
managed-identity-enabled: true
Derived from Authenticate Java applications to Azure services during local development and the Spring Cloud Azure documentation.
.NET (Azure.Identity)
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(
new Uri("https://kv-bookshelf.vault.azure.net/"),
credential);
KeyVaultSecret secret = await secretClient.GetSecretAsync("orders-db-connection-string");
Derived from DefaultAzureCredential Class and Authenticate .NET apps to Azure services.
Token flow: managed identity to a data-plane call
Workload identity federation
Workload identity federation lets a workload outside Azure (a GitHub Actions job, a Kubernetes pod, another
cloud’s workload) exchange its own platform-issued OIDC token for an Entra ID access token, via a federated
credential configured on an app registration — with no client secret stored or rotated anywhere. GitHub
Actions is the common case: the azure/login action authenticates using a federated credential tied to the
GitHub repository, environment, or branch, so the workflow authenticates as Bookshelf’s deployment app
registration without ever holding AZURE_CLIENT_SECRET. The workflow mechanics — job permissions
(id-token: write), the azure/login step itself, and how it fits alongside the rest of a build/push pipeline — are covered on Docker CI/CD with GitHub Actions; this page only covers the identity side:
creating the app registration, its service principal, the role assignment, and the federated credential subject
that must match the workflow’s repository/branch/environment exactly for the exchange to succeed.
The Bookshelf deployment identity is the app registration bookshelf-github-actions-deploy, trusted for pushes
to main of the <org>/bookshelf-catalog-api repository (replace <org> with your GitHub organization or user).
The federated credential’s subject must match the GitHub token’s sub claim exactly — no wildcards:
repo:<org>/<repo>:ref:refs/heads/<branch> for a branch, repo:<org>/<repo>:environment:<name> for a job tied
to a GitHub environment, or repo:<org>/<repo>:pull-request for pull-request-triggered workflows. A wrong
subject is accepted when the credential is created and only fails later, at token exchange time.
In the Azure portal
-
In the Microsoft Entra admin center (or Microsoft Entra ID in the Azure portal), open App registrations and select New registration. Enter
bookshelf-github-actions-deploy, keep Single tenant, and select Register. Registering through the portal also creates the app’s service principal in your tenant. -
On the new registration, select Certificates & secrets, open the Federated credentials tab, and select Add credential.
-
For Federated credential scenario, choose GitHub Actions deploying Azure resources; enter the Organization and Repository, set Entity type to Branch with
main, and give the credential a Name (for examplegithub-main). Issuer, Audiences and Subject identifier fill in automatically. Select Add. -
Open
rg-bookshelf, select Access control (IAM) → Add → Add role assignment, choose Contributor (or a narrower role that covers only what the pipeline deploys), and on Members pick User, group, or service principal and selectbookshelf-github-actions-deploy. Select Review + assign. -
From the app registration’s Overview, copy the Application (client) ID and Directory (tenant) ID; with the subscription ID, store them as the
AZURE_CLIENT_ID,AZURE_TENANT_IDandAZURE_SUBSCRIPTION_IDGitHub secrets thatazure/loginreads. None of them is a secret credential — there is no client secret.
With the Azure CLI
# App registration and its service principal
APP_ID="$(az ad app create --display-name bookshelf-github-actions-deploy --query appId -o tsv)"
SP_OBJECT_ID="$(az ad sp create --id "$APP_ID" --query id -o tsv)"
# Let the pipeline deploy into rg-bookshelf only
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role Contributor \
--scope "$(az group show --name rg-bookshelf --query id -o tsv)"
# Trust GitHub's OIDC tokens for pushes to main of <org>/bookshelf-catalog-api
cat > github-main.json <<'EOF'
{
"name": "github-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:<org>/bookshelf-catalog-api:ref:refs/heads/main",
"description": "GitHub Actions deploys from main",
"audiences": ["api://AzureADTokenExchange"]
}
EOF
az ad app federated-credential create --id "$APP_ID" --parameters github-main.json
# Values for the AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_SUBSCRIPTION_ID GitHub secrets
echo "$APP_ID"
az account show --query "{tenant: tenantId, subscription: id}" -o table
Derived from az ad app create, az ad sp create and az ad app federated-credential create.
Microsoft Entra ID vs. Microsoft Entra Domain Services
Microsoft Entra ID is a modern, internet-native identity provider built around OIDC, OAuth 2.0 and SAML — it has no concept of an LDAP bind, Kerberos ticket, or NTLM domain join, and Bookshelf’s own applications never need one. Microsoft Entra Domain Services provides a separate, managed service that does speak those legacy protocols: a classic Active Directory Domain Services-compatible domain (LDAP, Kerberos, NTLM, group policy) running as a managed Azure service, synchronized from the Entra ID tenant. It exists for the case Bookshelf does not have — a lift-and-shifted legacy application, or third-party software, that can only authenticate against a traditional Windows domain and cannot be updated to speak OIDC/SAML directly. Reaching for Domain Services is a sign that an application needs to be modernized onto Entra ID directly, not a permanent architecture choice.
Clean up
Remove what this page created, in dependency order: the app registration (deleting it also removes its service principal and federated credential), the custom role (which can only be deleted once nothing is assigned to it), and the user-assigned identity; deleting the resource group, if nothing else uses it, removes the role assignments scoped to it along with every resource inside.
az ad app delete --id "$(az ad app list --display-name bookshelf-github-actions-deploy --query '[0].appId' -o tsv)"
az role definition delete \
--name "Bookshelf Secret Reader (custom)" \
--custom-role-only true \
--scope "$(az group show --name rg-bookshelf --query id -o tsv)"
az identity delete --name id-catalog-api --resource-group rg-bookshelf
# Only when tearing down the whole Bookshelf environment
az group delete --name rg-bookshelf --yes --no-wait
Derived from az ad app delete, az role definition delete and az identity delete.
Related pages
-
Social Login and Federation and Authorization Server & Social Login cover OIDC/OAuth2 login flows from the application side.
-
JWT, OIDC and Keycloak covers the same concepts against Keycloak as the identity provider.
-
Key Vault and Secrets covers what
catalog-apistores inkv-bookshelfonce it can read it. -
Docker CI/CD with GitHub Actions covers the GitHub Actions workflow that uses the federated credential described above.
References
-
Microsoft Learn — Application and service principal objects in Microsoft Entra ID
-
Microsoft Learn — What is Azure role-based access control (Azure RBAC)?
-
Microsoft Learn — Create or update Azure custom roles using Azure CLI
-
Microsoft Learn — What are managed identities for Azure resources?
-
Microsoft Learn — Authenticate Java applications to Azure services during local development
-
Microsoft Learn — Configure a Spring Boot app with Azure Key Vault
-
Microsoft Learn — Connect GitHub Actions to Azure using OpenID Connect