Signing in is not authorization: app access is not agent access
Most “AI app” demos collapse three different questions into one login button:
- Can this person open the shell?
- Which agents are they allowed to talk to?
- Which knowledge are they allowed to see, download, or ground answers on?
In a real enterprise agent webapp, those are three different control planes. Treat them as one and you get either a security hole or a support nightmare.

Figure 1. SPA calling a web API. Authentication gets you a token; authorization still has to happen on the API. Source: Microsoft Learn: authentication flows and app scenarios.
I learned this while shaping access for a Foundry-backed agent front door. The product feels like ChatGPT. The entitlement model has to feel like a line-of-business system.
The core idea
App access answers authentication and presence. Agent access answers authorization and scope.
- App access. Microsoft Entra decides whether you can sign into the SPA (group assignment, Conditional Access, network rules). That only proves you are allowed in the building.
- Agent access. The API decides which agents appear, which chat streams are allowed, and which knowledge paths exist for you. That belongs in app roles (or equivalent claims) on the resource API, checked on every sensitive call.
- Knowledge access. Browse, download, upload, reindex, and citation fetch must use the same entitlement story as chat. If chat is locked but content download is open to any signed-in user, you do not have a knowledge boundary. You have a UI suggestion.
Signing in is necessary. It is not sufficient.
A model that stays explainable
1. One sign-in gate for the shell
Keep a clear “who may use the app at all” assignment (often an App Users group on the SPA). Dynamic groups are fine when the rule is org-wide. Do not overload this group with every specialized permission. Its job is the front door.
2. App roles on the Backend API for what you can do
Map roles to capabilities, not to screens:
- roles that mean “may use agent X”
- roles that mean “may manage knowledge category Y”
- a break-glass admin that implies both where that is intentional
Enforce on the API. Filter GET /api/agents so the catalog the UI renders is already honest. Return 403 on chat if someone tampers with agent ids. Do not “hide the button” and call it security.

*Figure 2. Declare app roles on the API app registration, then assign them to users or groups. Source: Microsoft Learn: add app roles.
3. Frontend speaks capabilities, not JWTs
The SPA should ask the API “what can I manage / browse?” and render that. Decoding roles in the browser duplicates policy and drifts. One access payload beats three copies of role string constants in React.
4. Library admin can imply agent use, deliberately
If someone can manage a specialized knowledge pack, they usually need the matching agent. Encode that as an explicit implication in one authorization service, not as tribal knowledge. General-only users should never see specialized packs in browse, download, or retrieval scope clamps.
5. Citations are an access path
Users will open the PDF the model cited. If that blob endpoint ignores the same path rules as the library, your “restricted knowledge” story is cosplay. Gate content reads like you gate chat.
Failure modes I design against
Signed-in = trusted. Every employee can open the app, therefore every employee sees every agent and every folder. That is a demo posture, not a production posture.
Groups everywhere, roles nowhere. Nested Entra groups for every folder feel flexible until Graph checks, token size, and “why doesn’t my access update” dominate the backlog. Prefer app roles on the API for product capabilities; use groups to assign those roles.
UI-only filtering. Agent picker hides options; API still accepts any agent id. Assume a curious user and a proxy.
Browse open, chat closed. You blocked the specialized agent but left library list/download open to all authenticated users. Same data, different door.
Manage without retrieve (or the reverse) without saying so. Decide whether “can manage this category” implies “can use the agent that reads it.” Then write it down and test it.
Trade-offs
Stricter roles mean more Entra work and more test matrices (general-only, specialized-only, admin, break-glass). You will negotiate with identity owners about who creates app roles versus who assigns groups.
On-behalf-of / multi-resource tokens add moving parts. Incomplete consent shows up as “random” platform failures that look like model bugs.
Network controls (private endpoints, IP allowlists) protect the plane; they do not replace per-agent authorization. Both layers matter; they solve different threats.
What I would put on an ADR
- App Users (or equivalent) = shell only.
- Backend API app roles = agents + knowledge categories.
- Single authorization module used by agent list, chat, library, and content.
- SPA consumes capability endpoints; no role parsing in the client.
- Automated tests for “general-only cannot browse or retrieve specialized paths.”
- Explicit implication rules (manage ⇒ use) documented and unit-tested.
Primary references: Microsoft Learn on Entra app roles, SPA calling a web API, and verifying scopes and app roles.
Closing
Enterprise agent apps fail identity in boring ways. Not because the model hallucinated a permission, but because we pretended ChatGPT’s “everyone in the tenant” mental model would survive contact with real knowledge packs.
If your architecture diagram has one box labeled Auth, redraw it.
Front door. Catalog. Corpus. Three questions. Three answers.
