devinside.Marcin Borecki Ideas. Systems. Evidence.
โ† The notebook

Engineering

Deployment, Runtime and Operator: Three Identities in One Azure Service

Separate deployment, runtime and operator authority with actual local JWT checks, then inspect an explicitly undeployed Azure identity mapping.

Explore the mechanism โ†“
Editorial ink and watercolor illustration for Three identities should not become one key

START WITH THE MECHANISM

Interactive companion and laboratory update: 12 November 2026. The editorial date places the topic on the timeline; this current explanation and its measured evidence retain their actual preparation and execution dates.

THE FULL INVESTIGATION

Deployment, Runtime and Operator: Three Identities in One Azure Service

Editorial context: 21 May 2026 ยท Prepared and updated: 12 November 2026
Dates and technology context

This is a retrospective editorial timeline. Topic context: 21 May 2026. First publicly available: 12 November 2026. Current text, visuals and laboratory revisions were prepared on 12 November 2026.

32 local JWT/HTTP/function checks, current Bicep compilation, runtime10.0.15 and MEAI11 integration are12Nov2026 evidence. No Azure deployment or Entra tenant was used.

Executed local JWT integration, with a compiled but undeployed Azure identity template. No Microsoft Entra tenant or Azure resources were used in the experiment.

A deployment pipeline can publish an application. The application can read a document. An operator can inspect a failure. Those three capabilities often appear under one convenient credential during development, even though they answer different questions and create different consequences.

The convenience becomes difficult to unwind when the first incident arrives. A person who needs diagnostic information is given access to source data. A process that only needs to read one collection inherits deployment privileges. A body field supplied by a caller begins to look like an authoritative tenant identifier because it is easier to pass through every layer than to reconstruct trusted context.

The useful design starts by naming the responsibility of each identity. Deployment changes what code runs. Runtime performs the application's authorized work. Operations inspects declared diagnostic surfaces. Each role needs an explicit resource and action boundary, and each boundary needs a negative test as well as a successful example.

This article implements a local reference API with those three roles. It uses actual ASP.NET Core JWT bearer validation, locally signed test tokens and real HTTP calls. A scripted Microsoft.Extensions.AI tool caller reaches a guarded application function. The experiment does not query account permissions, budgets, billing or quotas. Its signing keys are generated in memory for the run and disposed afterward.

The Azure counterpart is a Bicep candidate defining a runtime managed identity, an application host, an evidence container and scoped role assignments. It is compiled locally with an official Bicep binary. Compilation is not deployment, and the local role tests are not Azure RBAC integration tests. Keeping those evidence categories separate is part of the engineering result.

Define three responsibilities before choosing role names

01 / See the idea

Three roles do not mean one shared credential

Three roles do not mean one shared credential. The fixture gives deployment, runtime and operations separate subjects and scopes. This is an application-role contract, not a measured Azure RBAC assignment.

The fixture gives deployment, runtime and operations separate subjects and scopes. This is an application-role contract, not a measured Azure RBAC assignment.

Interpretation and evidence
  • Name the resource and action.
  • Test permitted and forbidden cells.
  • Deployment still controls code.
conceptual mapping of local fixture or compiled-only Azure template

The local API exposes three small surfaces. /deploy represents a delivery operation, /evidence returns a synthetic protected marker, and /ops returns a limited diagnostic view. The names make the fixture readable, but the real contract is a combination of role, scope and tenant.

A deployment token has deploy.write; a runtime token has evidence.read and tool.execute; an operator token has ops.read. All accepted requests also belong to the fixture tenant. These are application-defined claims, not the names of Azure built-in roles.

The expected matrix has three permitted cells and six denied cells. Deployment can use the deployment surface, runtime can read evidence, and the operator can inspect the diagnostic surface. Cross-role requests receive a denial even though the token itself is valid and correctly signed.

This distinction is fundamental. Authentication establishes that the token satisfies the selected issuer, audience, signature and lifetime checks. It does not imply that every authenticated caller may invoke every endpoint. A role name only becomes useful when the application interprets it against the resource and action being requested.

The local design deliberately makes the three identities separate subjects. That helps expose accidental reuse. A real deployment may use a workload identity for delivery, a managed identity for runtime and a human group for operations, but the exact mechanism must be selected for its environment rather than copied from the fixture's token strings.

The role matrix is also not a complete organizational separation-of-duties model. A principal able to change running code may indirectly cause that code to use its runtime identity. Denying that principal direct data-plane access does not make malicious deployment harmless. Delivery remains a high-trust capability requiring its own review and release controls.

Validate the token instead of trusting its decoded JSON

02 / See the idea

Authentication failure is different from a scope denial

Authentication failure is different from a scope denial. All four invalid-token classes returned401. Valid tokens with a disallowed role or tenant returned403, separating authentication from application authorization.

All four invalid-token classes returned401. Valid tokens with a disallowed role or tenant returned403, separating authentication from application authorization.

Interpretation and evidence
  • Verify issuer and audience.
  • Verify lifetime and signature.
  • Do not decode claims and assume trust.
actual check-results.json

A JWT can be decoded without being trusted. Its payload may contain exactly the role and tenant an application wants to see while its signature, issuer or audience is wrong. The reference therefore uses the actual JWT bearer middleware rather than manually reading claims from an unvalidated string.

The relevant validation configuration is explicit:

o.MapInboundClaims = false;
o.TokenValidationParameters = new()
{
    ValidateIssuer = true,
    ValidIssuer = issuer,
    ValidateAudience = true,
    ValidAudience = audience,
    ValidateLifetime = true,
    ClockSkew = TimeSpan.Zero,
    RequireExpirationTime = true,
    RequireSignedTokens = true,
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = key,
    ValidAlgorithms = [SecurityAlgorithms.RsaSha256]
};

The zero clock skew is a deliberate test setting that makes the expiry case unambiguous. A production choice needs to account for its issuer and clock assumptions rather than inherit this value because a laboratory used it.

The issuer and audience are fixture identifiers. There is no OpenID Connect discovery request, key rollover service or Microsoft Entra sign-in. The program creates an ephemeral RSA key and signs tokens only for its own local tests. This is a valid way to exercise token-validation behavior, not a recommendation to replace an organization's identity provider with a handwritten issuer. Microsoft's guidance distinguishes test-generated tokens from a production identity system. ASP.NET Core JWT bearer authentication.

The negative requests change one condition at a time: issuer, audience, expiry or signing key. Each is expected to fail authentication with 401. A separately valid token for the wrong tenant is expected to reach authorization and receive 403. That separation makes the observed failure informative instead of grouping every denial under a generic access error.

Neither token strings nor private signing material are written into the evidence report. The report records which condition was tested and the resulting status. The code retains the keys only inside the finite process that owns the fixture.

Derive tenant and role from validated context

03 / Watch the mechanism

Request data cannot replace validated context

Request data cannot replace validated context. A request body may contain role and tenant-looking fields, but the server-side function uses the principal created by JWT validation. The operator request remains denied.
Enlarge diagram

Static view. Play to see how the mechanism unfolds.

A request body may contain role and tenant-looking fields, but the server-side function uses the principal created by JWT validation. The operator request remains denied.

Interpretation and evidence
  • Keep identity outside tool arguments.
  • Validate the requested resource separately.
  • Use the real function-binding path.
conceptual mapping of local fixture or compiled-only Azure template

The tool endpoint accepts a resource request. It does not accept a role or tenant as authoritative command parameters. The caller may include extra JSON fields named role or tenantId, but the function uses HttpContext.User, which came from the validated token.

The same rule applies inside the model-facing function. A local method closes over the host's validated principal and checks the required scope before applying the synthetic effect. The scripted provider includes identity-looking arguments deliberately, but those arguments do not replace the principal held by the server.

The test sends the same body with an operator token and a runtime token. The operator request is denied before function invocation can produce an effect. The runtime request reaches the real FunctionInvokingChatClient path and produces exactly one recorded effect through the application guard.

This is an actual SDK integration, not a direct method call described as a tool test. The provider implements IChatClient and returns a controlled function request. The middleware binds it to an AIFunction created from the application method, sends back the function result and obtains a final scripted response. No live model is involved.

The trusted context remains useful even when the function schema changes. Resource identifiers are still untrusted input and must be checked, but the caller cannot gain authority by adding a plausible tenant string. A production schema might reject unknown fields explicitly; this fixture's central claim is narrower: additional fields do not participate in identity selection.

Immutable context also does not mean immutable authorization. The principal can remain the same while current application policy changes. The companion streaming test demonstrates that distinction by changing a live access flag or resource version after retrieval and before emission.

Match the local roles to Azure concepts carefully

Azure managed identity supplies an identity for an Azure-hosted workload without requiring the application to manage an ordinary credential. It can then be authorized to access a downstream resource that supports Microsoft Entra authentication. The identity mechanism and the permission assignment remain separate decisions. Managed identities overview.

The candidate template creates a user-assigned runtime identity and attaches it to the web application. It assigns Storage Blob Data Reader at the evidence-container scope. That expresses a narrow intended data-plane relationship: the runtime needs to read that evidence collection, not manage every storage account in a subscription.

The deployment principal is an input object ID. The template assigns Website Contributor at the application scope. This is a candidate delivery role, not a claim of the smallest possible custom role for every release pipeline. The built-in role includes broad website-management actions, so the template should be reviewed against the actual delivery mechanism before deployment. Website Contributor role.

The operator principal is an input group object ID with Monitoring Reader at the application scope. This is a control-plane and monitoring relationship. It does not make the logs safe by itself. If the application emits protected source text into monitoring data, a monitoring reader may receive that text through the diagnostic channel. The application must control what it records as well as who can read it. Monitoring Reader role.

The runtime role is similarly specific. Storage Blob Data Reader describes read access to blob data within the assigned scope. It is not an authorization policy for individual application users. The application may need a narrower tenant or document policy on top of its own ability to retrieve the data. Storage Blob Data Reader role.

The local JWT matrix does not verify any of these Azure assignments. It verifies an application-level analogue with real token validation. The template and official role definitions supply a mapping to review, while an actual Azure integration would require deployment and access tests in an explicitly authorized environment.

Compile a template without calling it a deployed system

05 / See the idea

Azure mapping has its own evidence gate

Azure mapping has its own evidence gate. The Bicep candidate separates scoped role assignments and compiles locally. No deployment, principal existence or managed-identity token acquisition was tested in Azure.

The Bicep candidate separates scoped role assignments and compiles locally. No deployment, principal existence or managed-identity token acquisition was tested in Azure.

Interpretation and evidence
  • Compilation is not deployment.
  • Bootstrap authority differs from delivery.
  • Review effective permissions before use.
conceptual mapping of local fixture or compiled-only Azure template

The Bicep file includes an App Service plan, a Linux web application, a storage account and evidence container, a runtime identity and three role assignments. It disables public blob access and shared-key access, requires HTTPS and supplies the runtime identity's client ID as application configuration.

The template does not include the fixture's private key, test tokens or issuer. Those are not deployment inputs. A production authentication configuration must point to the chosen identity provider and define its audience and validation requirements separately.

The deployment and operator principal IDs are parameters with no invented values. Supplying a syntactically valid object ID would not prove that the principal exists or that the deployment identity has authority to assign roles. The identity that bootstraps the infrastructure has different requirements from the app-scoped delivery identity granted by the template.

The local Bicep compiler checks the source and emits an ARM template. That is useful evidence about syntax, types and template construction. It does not check subscription policy, regional availability, naming uniqueness, role-assignment propagation or whether an application successfully obtains and uses a managed-identity token. Bicep tooling installation.

No deployment command was run. There were no Azure resources, charges, account queries or credentials involved. The resulting JSON is a candidate artifact for review, not a completed production environment.

The recorded compiler was Bicep CLI 0.49.2, commit b34271ac51. Its downloaded executable matched the official release asset SHA-256. Building platform.bicep produced platform.json with exit code zero and no reported warnings. The source and compiled-file hashes are retained in bicep-evidence.json. No Azure deployment or remote validation command was executed, so this result is labelled compiled_not_deployed.

Verify separation through positive and negative paths

04 / See the idea

The local role matrix was actually exercised

The local role matrix was actually exercised. All nine role-route combinations are read from the accepted report. The three permitted cells work, so denial is not achieved by disabling every route.

All nine role-route combinations are read from the accepted report. The three permitted cells work, so denial is not achieved by disabling every route.

Interpretation and evidence
  • Positive paths are necessary evidence.
  • Cross-role access remains forbidden.
  • These are local API roles.
actual check-results.json

The role matrix is only persuasive if its valid cells work. Denying every request would make the negative cases green while producing an unusable service. The suite therefore checks all nine role-route combinations, not only the six forbidden ones.

It then sends four invalid token classes to the evidence endpoint and confirms authentication failure. The wrong-tenant case uses a valid signature and the otherwise appropriate runtime role, so its denial cannot be attributed to malformed token syntax. This isolates the tenant boundary.

The body-escalation test adds privileged-looking values to a tool request while using an operator token. The absence of an effect confirms that those values do not override validated context. A runtime token with the same resource request then passes through actual function invocation and produces the expected effect.

The diagnostic endpoint returns counters and selected event categories. After the streaming scenarios, the test searches its body for the synthetic protected marker and expects it to be absent. This is a concrete check of the fixture's diagnostic projection, not proof that arbitrary logs in a larger service cannot contain sensitive data.

The report contains the observations needed to distinguish these outcomes. It does not retain bearer tokens or a complete request-header dump. An identity test can preserve useful evidence without turning its results into a credential archive.

The accepted shared run passed 32 checks on .NET 10.0.15. The nine role-route combinations produced 200 only for the intended role and 403 for the other roles. Tokens with wrong issuer, audience, expiry or signature produced 401. A valid runtime token for the other tenant produced 403. A privileged-looking request body did not elevate the operator, while the runtime tool request exercised the real Microsoft function path and produced one effect.

Stream scenario Protected marker in response Tool effects Active requests after completion
Access revoked after retrieval No 0 0
Resource version changed after retrieval No 0 0
Access and version unchanged Yes 1 0
Client disconnected before release No protected content released 0 0

The operator diagnostic response did not contain the protected marker. The job fixture separately verified capacity one, success on the third scripted attempt, Unresolved after three unsuccessful attempts, stored success after reopening a connection, and Stopped with zero active work after graceful cancellation. These are functional outcomes; no latency, model quality or cloud availability claim is inferred from them.

Revocation requires a policy about time

Validating a token at the start of a request does not guarantee that every permission remains appropriate until the response ends. A tenant's access can be revoked, a resource can change version, or an operation can continue long after the caller's interactive session.

The reference keeps a live application policy state separate from the validated principal. At each protected evidence and tool boundary it checks role, scope, tenant and the current access condition. A version captured for the request must still match when protected content is about to be released.

This is not Microsoft Entra token revocation. The test changes local application state under controlled barriers. It demonstrates how the application can respond to a known policy change, while leaving the identity provider's propagation, token lifetime and cache behavior outside the result.

In the Azure mapping, those distinctions become operationally important. Removing an Azure role assignment, revoking an application permission and invalidating a previously issued token are not interchangeable actions with identical timing. A deployment plan should state which mechanism the service relies on and what delay or residual authority it accepts.

The local example chooses a conservative response: do not release the protected marker or perform the tool effect after the tested condition changes. It preserves a generic stopped state for the caller. The companion article examines that streaming boundary in detail rather than claiming that one role matrix solves every long-running authorization problem.

Deployment power is still a privileged capability

Separating credentials reduces accidental sharing, but it does not automatically isolate all consequences. A principal allowed to publish arbitrary application code can potentially alter how the application uses its runtime identity. That principal therefore remains part of the trusted software-delivery chain even when it has no direct blob-data role.

This is why the template's deployment assignment should not be presented as proof that deployers are technically incapable of influencing protected data. It separates direct permissions and intended responsibilities. Stronger separation requires controls over code review, artifacts, release approval, hosting configuration and the identity permissions available to deployed code.

Operators have a related boundary. Read-only monitoring access can still reveal information if diagnostics contain business payloads. The least-privilege question includes both who can read the channel and what the application writes into it. An apparently harmless trace attribute can expand the audience for a document.

The fixture makes these relationships inspectable without attempting a full organizational security model. Its operator route returns a deliberately restricted projection. Its runtime route is protected separately. Its deployment route is only a synthetic marker, not a local backdoor that actually rewrites the process.

A real delivery pipeline should carry these distinctions into its acceptance evidence. Show which principal publishes, which principal reads data, which people read diagnostics and which mechanism prevents unreviewed code from changing that arrangement. A diagram containing three identity icons is a starting point, not the finished proof.

Bootstrap authority is different from routine delivery

The template creates role assignments, so the identity applying that template must be able to perform those infrastructure operations. The delivery principal that the template later grants app-scoped access does not thereby gain permission to bootstrap the entire environment. These are different moments with different responsibilities.

Confusing them often leads to a broad permanent credential. A team grants a pipeline enough authority to create everything, then keeps using the same principal for ordinary application releases. The resulting deployment works, but its credential now carries infrastructure powers that a routine code update may not need.

The reference avoids hiding that distinction behind automation. The deployment principal ID is an input, and the template's role assignment is visible. It does not include a magic credential that can both create its own authority and run every later release. The organization must decide who provisions the initial environment and who approves changes to those assignments.

A safe deployment test would therefore have at least two stages. First establish the infrastructure and its role relationships under an appropriately authorized bootstrap process. Then perform an ordinary release with the intended delivery principal and check both what succeeds and what it cannot do directly. The local JWT route named deploy is only an application analogue; it does not execute either Azure stage.

This separation also clarifies rollback. Reverting application code should not silently widen runtime data access or replace the operator's scope. Infrastructure rollback can have different consequences from code rollback, especially when identities or resources have been deleted. A production runbook should name those operations rather than assume one universal undo button.

Review the resource scope, not just the role label

A permission assignment combines a principal, a role and a scope. The same role at a container, application, resource group or subscription can create very different effective authority. Reading only the role's friendly name is therefore insufficient.

The candidate gives the runtime blob reader role at the evidence container. It gives delivery and monitoring roles at the application. That expresses the intended narrow relationship in the template, but inherited permissions elsewhere could still broaden a real principal's authority. This project does not query a user's tenant to discover or certify those effective permissions.

The correct next evidence in an authorized Azure environment would include the actual principal and scope relationship, the operations required by the application and negative probes outside that scope. It should also record propagation delay and existing inherited assignments if those affect the conclusion. A template diff alone cannot tell us the final effective access of a live identity.

Application tenancy adds another layer. A runtime identity may read the evidence container for many users, while each user may see only a subset of its documents. Giving the workload the right Azure role does not remove the need for per-request authorization inside the application. The fixture's tenant check represents that latter boundary.

The operator surface has a similar distinction. A monitoring reader is an infrastructure role; the local /ops endpoint is an application projection. Neither automatically proves the other. Their intended relationship can be documented, but each requires its own test when the production integration exists.

Do not turn fixture cryptography into a credential pattern

The ephemeral issuer is useful because the test controls its inputs. It can deliberately sign a token for the wrong audience, use another key or set an expired lifetime, then observe the real middleware's response. The test does not need a persistent secret or a live tenant to exercise those validation branches.

That convenience should remain confined to the fixture. A production identity system has issuance policy, key rotation, discovery, incident response and client flows that are absent here. The local token generator cannot establish those responsibilities simply because its signatures are valid.

The report consequently stores only scenario labels and observed outcomes. It does not preserve the tokens for readers to copy, export the private key or add the fixture issuer to global trust. Reproduction creates a new key inside a new process and tests the same contract with different cryptographic material.

This is a useful general pattern for security examples: keep the mechanism real, keep the credentials ephemeral and keep the claim narrow. It allows another engineer to reproduce a validation failure without encouraging the use of demonstration credentials in an actual service.

Reproduce the local evidence without manufacturing cloud evidence

The laboratory pins SDK 10.0.400 and runtime 10.0.15. Its package lockfile records the JWT bearer, Microsoft AI and SQLite dependencies. The runner starts a temporary loopback HTTP listener, creates ephemeral signing keys in memory and removes its local job database after completion.

The functional command takes a report path and an owned working directory. The report is retained; keys, connections, the listener and temporary database are not. The Bicep compiler is a separately pinned local tool downloaded from the official release repository and used only to build the supplied template.

dotnet restore --locked-mode
dotnet run -c Release -- ../check-results.json <owned-work-directory>
bicep build platform.bicep --outfile platform.json

The commands have different evidential meanings. The application run verifies local authentication and policy behavior. The Bicep build verifies a template artifact. Neither is an Azure deployment. Keeping those labels attached prevents a useful laboratory from becoming a misleading cloud case study.

The practical result is a sharper contract for identity work. A deployment identity changes code, a runtime identity performs declared application operations, and an operator identity receives a defined diagnostic view. Their boundaries can be expressed in code and tested independently before a live cloud environment is introduced. That makes the next integration test smaller, and the eventual claim of separation easier to assess honestly.

Source code and execution evidence

Download the complete lab, source record and raw execution results. This package supports the specific execution claims in this article. It is not a production certification.

THE AUTHOR

Marcin Borecki

Developer, entrepreneur and business angel. Writing about AI, software, data and the evidence behind technical decisions.

About the author โ†’