Entra ID and Identity Fundamentals
Why this matters
Every az login, every managed identity you attached, every az role assignment create you ran in tracks 06, 07, and 10 was talking to one thing:
your Entra ID tenant (the service formerly and still often called Azure
Active Directory / Azure AD). Until now it was invisible plumbing. This module
makes the plumbing explicit, because you cannot reason about service
principals, managed identity, or workload identity federation without a solid
mental model of tenants, the difference between proving who you are and
being allowed to do something, and what an app registration actually is.
Concepts
The tenant is your directory
An Entra ID tenant is a dedicated instance of the directory service — the
authoritative database of who and what exists in your organization: users,
groups, and application identities. It has a globally unique tenant ID (a
GUID) and one or more domains (e.g. contoso.onmicrosoft.com). Every Azure
subscription trusts exactly one tenant for authentication; when you ran
az login in the AKS track and picked a subscription, you were authenticating
against that subscription's tenant. A crucial split to hold onto from the
start: the tenant is the identity plane, the subscription is the resource
plane. A tenant can trust many subscriptions; a subscription trusts one
tenant. Deleting a resource group never touches the tenant; deleting a user
never touches your VMs. This is the same conceptual boundary you saw when the
07-aks module 07 cleanup noted that a Key Vault (a resource) can outlive an
az group delete — identities and resources have separate lifecycles.
Users and groups
A user is a human (or a break-glass admin account) that signs in with
credentials. A group is a named collection of users (and sometimes other
groups or service principals). You met groups already: in
07-aks/07 you
bound an Entra ID group object ID to a Kubernetes ClusterRoleBinding so
that team membership — not a hand-edited kubeconfig — controlled cluster
access. The reason groups matter for a platform engineer is you assign
permissions to groups, not individuals: role assignments to a group flow to
every member automatically, and revoking access is a matter of removing
someone from the group. This is exactly the least-privilege, minimize-blast-
radius thinking from 11-security-deep-dive,
applied to who-can-do-what.
App registrations vs. enterprise applications vs. service principals
This is the single most confusing corner of Entra ID, so pin it down now — it recurs in every later module. When you register an application you get two linked objects:
- An app registration (also called the application object) is the global definition of your app: its name, its client ID, what secrets/certificates it can authenticate with, what API permissions it requests, and — critically for module 03 — its federated credentials. There is exactly one app registration per application, and it lives in the tenant where the app was registered.
- A service principal (also called the enterprise application in the portal) is the local representation of that app inside a specific tenant — the thing that actually gets role assignments and can be granted access to resources. When an app is used in a tenant, a service principal for it is created there.
Analogy: the app registration is the class, the service principal is the
instance. You define the app once (registration); each tenant that uses it
gets its own service principal to hang permissions on. For single-tenant apps
(everything you will build in this track) there is one registration and one
service principal, one-to-one, in your tenant — but knowing they are separate
objects explains why az ad app and az ad sp are different command families
in module 01.
Authentication vs. authorization
You saw this split named in 07-aks/07 (you could authenticate via Azure AD yet be authorized for nothing). Make it a reflex:
- Authentication (authn) = proving who you are. The tenant checks your credential (password, certificate, a federated OIDC token) and, if valid, issues you a token. Output: "yes, you are this identity." That is all it says.
- Authorization (authz) = deciding what you may do. A separate system — Azure RBAC (module 04) — checks whether your identity has a role assignment granting the action you attempted on the resource you attempted it on. Output: "allowed" or "Forbidden / 403."
Almost every identity failure you will diagnose in module 07 is one of these
two failing, and the whole skill is knowing which. A wrong secret or an
expired token or a subject-claim mismatch is an authn failure (you never
get a valid token). A valid token but a missing role assignment is an authz
failure — the 403/Forbidden you saw crash-loop a Container App in
06/06
when the role was missing.
OAuth2 and OIDC, just enough
You do not need to implement these, but you need the vocabulary because every later mechanism is built on them:
- OAuth2 is the framework for delegated authorization — how a client gets an access token to call an API (like the Azure Resource Manager API, or Microsoft Graph) on behalf of some identity. The token is a signed, time-limited credential (a JWT) that the resource validates.
- OIDC (OpenID Connect) is a thin authentication layer on top of OAuth2
that adds an ID token — a JWT asserting who the identity is, with
standard claims like
iss(issuer — who minted the token),sub(subject — which identity), andaud(audience — who the token is for). - The
sub(subject) claim is the one to remember: in workload identity federation (module 03), Azure decides whether to trust an external token by matching itssubclaim (e.g. GitHub'srepo:my-org/my-repo:ref:refs/heads/main) against a federated credential you configured. When the OIDC login in module 03 fails, it is almost always asubmismatch. You will hear "subject claim" again and again — this is where it comes from.
Command reference
| Command | What it does | Example |
|---|---|---|
az account show |
Shows the current subscription and its tenant ID | az account show --query "{sub:id, tenant:tenantId, user:user.name}" -o jsonc |
az ad signed-in-user show |
Shows the Entra ID object for the currently logged-in user | az ad signed-in-user show --query "{name:displayName, id:id, upn:userPrincipalName}" -o jsonc |
az ad user list |
Lists users in the tenant | az ad user list --query "[].{name:displayName, upn:userPrincipalName}" -o table |
az ad group create |
Creates a security group | az ad group create --display-name "platform-admins" --mail-nickname "platform-admins" |
az ad group member add |
Adds a member to a group | az ad group member add --group "platform-admins" --member-id <user-object-id> |
az ad group member list |
Lists group members | az ad group member list --group "platform-admins" -o table |
az ad app list |
Lists app registrations (application objects) | az ad app list --query "[].{name:displayName, appId:appId}" -o table |
az ad sp list |
Lists service principals (enterprise apps) | az ad sp list --show-mine -o table |
Flag-by-flag breakdowns:
az ad group create --display-name "platform-admins" --mail-nickname "platform-admins"
--display-name— the human-readable group name shown in the portal and used by--groupin later commands.--mail-nickname— a mail alias Entra ID requires even for non-mail-enabled security groups; use the same slug as the display name. It must be unique-ish and mail-safe (no spaces).
az ad group member add --group "platform-admins" --member-id <user-object-id>
--group— the group's display name or object ID.--member-id— the object ID (a GUID) of the user/SP/group being added, not the display name or UPN. Get a user's object ID withaz ad user show --id <upn> --query id -o tsv.
az account show --query "{sub:id, tenant:tenantId, user:user.name}" -o jsonc
--query— a JMESPath projection pulling out just the subscription ID, tenant ID, and signed-in user in one object; the whole point of this exercise is to see the sub-vs-tenant split on your own account.
Hands-on exercises
See your tenant and subscription split. Run
az account show --query "{sub:id, tenant:tenantId, user:user.name}" -o jsonc. Write down which GUID is the subscription and which is the tenant. Verify: the two GUIDs are different, and re-running afteraz account set --subscription <other-sub>(if you have a second subscription) changes thesubbut very likely keeps the sametenant.Look at yourself as a directory object. Run
az ad signed-in-user show --query "{name:displayName, id:id, upn:userPrincipalName}" -o jsonc. Note your object ID — this is the GUID you would pass as--assigneeto a role assignment or as asubjectsobject ID in a Kubernetes binding. Verify: theidhere is a different GUID from both your subscription and tenant IDs. Identity objects have their own IDs.List the app registrations and service principals already in your tenant. Run
az ad app list --query "[].{name:displayName, appId:appId}" -o tableandaz ad sp list --show-mine -o table. Verify: you almost certainly already have service principals from earlier tracks (for example, ones created byaz ad sp create-for-rbac, or the CLI's own first-party apps). Notice the SP list is longer — every managed identity and first-party Microsoft app also shows up as a service principal.Create a security group. Run
az ad group create --display-name "platform-admins" --mail-nickname "platform-admins", then capture its object ID:az ad group show --group "platform-admins" --query id -o tsv. Verify: the group appears inaz ad group list --query "[?displayName=='platform-admins']" -o table.Add yourself to the group. Get your object ID (
az ad signed-in-user show --query id -o tsv), thenaz ad group member add --group "platform-admins" --member-id <your-object-id>. Verify:az ad group member list --group "platform-admins" -o tablelists you. (Later modules assign roles to this group, not to you directly — that is the point of using a group.)Inspect a token's claims (conceptual, no secret). Run
az account get-access-token --query "{expires:expiresOn}" -o jsonc. This is an OAuth2 access token the CLI holds for the ARM API. Verify: it has an expiry a short time in the future — tokens are time-limited, which is exactly why a stored, long-lived secret (module 01) is a different and worse thing than a short-lived token. (Do not paste the raw token anywhere; treat it as a live credential.)Distinguish authn from authz on purpose. Pick any resource group you own and run a harmless read, e.g.
az group show --name <some-rg>. It works because you are both authenticated and authorized (Owner/Contributor). Now run something you are almost certainly not authorized for, like creating a role assignment at subscription scope for a random principal (az role assignment create --assignee <your-id> --role Owner --scope /subscriptions/<sub-id>— expect this to be denied unless you are a subscription Owner/User Access Administrator). Verify: the failure is a403/authorization error, not a login prompt — you were authenticated fine; you just were not authorized. That distinction is the whole module.Diagnose-and-fix: "member-id" rejected. Deliberately run
az ad group member add --group "platform-admins" --member-id "platform-admins"(passing a name where an object ID is required). Observe the error. Diagnose:--member-idneeds a GUID object ID, not a display name. Fix: resolve the name to an ID first (az ad user show --id <your-upn> --query id -o tsv) and pass that. Verify the member is added. This is the same class of mistake — name vs. object ID — that trips up--assigneein later role-assignment exercises.
Independent challenge
Without copying any command block above, model your own two-tier access story
in the directory. Create two security groups representing two real teams — say
app-developers and platform-admins — put your own user in both, then design
(on paper, or in a scratch note) which of the Azure resource types you have
already worked with in tracks 06, 07, and 09 each group should be able to
touch and at what level, citing the least-privilege reasoning from
11-security-deep-dive. Do not assign
any roles yet — that is module 04 — but produce a table mapping group →
resource → intended permission → why. The point is to practice thinking in
terms of groups and intent before you touch RBAC mechanics, so that when
module 04 gives you az role assignment create you already know what you
mean to grant and to whom.
Stuck? One hint
Start from the boundary this module opened with: the tenant is the identity
plane, the subscription is the resource plane. Your groups live in the
tenant; the things you want to gate (a Key Vault, an AKS cluster, an ACR) live
in subscriptions/resource groups. So every row in your table is "this tenant
group should get this role scoped to this resource-plane object" — which is
exactly the shape of an az role assignment create you will write in module
04. You are just writing the intent now.
Common mistakes & troubleshooting
- Confusing tenant ID, subscription ID, and object ID. They are three
different GUIDs for three different things (directory instance, billing/
resource container, a specific identity object). Mixing them up is the root
of most "wrong ID" errors in later modules. When a command wants
--assignee, it wants an object ID; when it wants--scope, it wants a resource ID (which contains the subscription ID). - Treating app registration and service principal as one thing. They are
linked but distinct objects (
az ad appvs.az ad sp). Deleting the app registration also removes its service principal, but editing one via the wrong command family silently does nothing useful. - Assigning permissions to individual users instead of groups. It works, but it does not scale and it makes offboarding error-prone. Prefer groups from day one — it is the identity-plane version of the least-privilege discipline from track 11.
- Passing display names where object IDs are required.
--member-id,--assignee, and Kubernetessubjectsobject IDs all want GUIDs. Resolve names to IDs explicitly (... --query id -o tsv) and pass the ID. - Assuming
az loginpicked the tenant you meant. If you belong to more than one tenant,az login --tenant <tenant-id>(andaz account set) puts you where you intend. A "resource not found" that makes no sense is often "you are authenticated against the wrong tenant."
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- What is the difference between a tenant and a subscription, and which one does authentication happen against?
- An app registration and a service principal are two linked objects. In the "class vs. instance" analogy, which is which, and which one receives role assignments?
- State the difference between authentication and authorization in one
sentence each, and say which one a
403/Forbiddenis. - Why do you assign permissions to groups rather than to individual users?
- In an OIDC token, what does the
sub(subject) claim assert, and which later module makes matching it the whole game? - You run a read command and it succeeds; you run a role-assignment-create
command and get a
403. Were you authenticated in the second case? How do you know? - A command asks for
--member-idand you pass a group's display name; it fails. What kind of value did it actually want, and how do you get it?
Show answers
- A tenant is the directory/identity instance (users, groups, app identities) with its own tenant ID; a subscription is a resource/billing container. Authentication happens against the tenant — the subscription just trusts one tenant to vouch for identities.
- The app registration is the class (the global definition of the app); the service principal is the instance (its local representation in a tenant). The service principal is what receives role assignments and access grants.
- Authentication = proving who you are (you get a token or you do not).
Authorization = deciding what you may do (you have a role assignment or
you do not). A
403/Forbiddenis an authorization failure — you were authenticated fine. - Because permissions assigned to a group flow to all members automatically, and access is granted/revoked by group membership rather than by editing every individual's assignments — it scales and it makes offboarding safe.
- The
subclaim asserts which identity the token is for. Module 03 (workload identity federation) makes matching that subject claim against a configured federated credential the whole game. - Yes, you were authenticated in the second case — you got far enough to be evaluated for authorization and denied. An authentication failure would have blocked you with a login/credential error before any permission check.
- It wanted an object ID (a GUID), not a display name. Resolve it first,
e.g.
az ad group show --group <name> --query id -o tsv(oraz ad user show --id <upn> --query id -o tsvfor a user), and pass that GUID.
Next
01-service-principals-in-depth — the first concrete application identity: creating a service principal, the choice between a client secret and a certificate, and why long-lived secrets are the liability that motivates everything after it.