Azure RBAC answers whether a principal may touch an Azure resource. An app role answers whether a caller may invoke an operation. Neither answers whose data — that is layer 3, and it is the one most often missing.
The tenant predicate reads tid from the validated token and never from the request body, a header or a path parameter. This single rule is the difference between multi-tenant and multi-tenant-shaped.
Deny by default at every gate. An unrecognised scope, an absent role or a mismatched tenant all produce a 403 with a reason code, never a permissive fallback.
Design consequences
Custom Azure roles are permitted only where a built-in role is genuinely insufficient; each requires a documented justification and an annual review. Today there are seven.
ABAC conditions on role assignments narrow storage access by tag and path, which removes a class of custom roles that existed only to scope a container.
The audit record names the layer that made the decision. Without it, a support engineer cannot distinguish a missing role from a tenant mismatch, and users get told to ask for more access than they need.
Risks
Layer 3 lives in application code, so its correctness depends on the product team. Mitigation is a shared authorization library plus a contract test in the pipeline, not a policy document.
Delegated scopes with broad meaning — anything resembling read-all — leak across tenants if layer 3 is weak. Scope design is reviewed alongside API design, not after it.
JWKS caching for 24 hours means a signing-key rotation is not picked up instantly. Entra publishes keys ahead of use, so the risk is an emergency revocation, which is handled by cache invalidation in the runbook.