,

Azure OpenAI Entra ID Authentication, Managed Identities, and Encryption (Azure Gen AI Series, Part 10)

Move Azure OpenAI off static API keys to Microsoft Entra ID and managed identities, disable local auth without locking yourself out, and know when customer-managed keys are worth the cost.

Azure Gen AI Series · Part 10 of 30

The API key sitting in your app configuration is a bearer credential with no expiry, no name attached, and full data-plane access to your model. Anyone who reads it can call your deployment as you, from anywhere, until you notice and rotate it. That one string is the first thing I take out of a production Azure OpenAI design, and this part is how I replace it.

Key takeaways: Two credential types reach an Azure OpenAI endpoint, a static API key or a Microsoft Entra ID token. Prefer Entra. Assign the Cognitive Services OpenAI User role for inference instead of handing out the account keys. Give your app a managed identity so no secret lives in code or config. Set disableLocalAuth once Entra works, and keep a break-glass path so you do not lock yourself out. Encryption at rest is already on with Microsoft-managed keys; reach for customer-managed keys only when a written policy needs key control you can revoke.

Where identity sits in an Azure OpenAI call

Microsoft Entra ID, renamed from Azure Active Directory, is the identity service that issues and checks tokens for everything in Azure. An API key, by contrast, is a symmetric secret the resource itself mints, with no link to any person or workload. Every call to your endpoint carries one of these two credentials in a header. Key auth sends api-key with the raw secret. Entra auth sends Authorization with a Bearer token. The service validates whichever arrives before it routes the prompt to the model.

The difference is who the credential represents. A key represents nothing, so a leaked key is anonymous access that logs show as the resource calling itself. An Entra token represents a named identity, a user or a workload, and it carries role assignments that decide what that identity may do. When something goes wrong at 2am, one of these tells you who did it and the other does not. That is the whole argument in one sentence, and everything below is how you act on it.

The Entra path to a modelA workload identity gets a token, the gateway checks the role, then the model runsYour appmanaged identityno stored secretEntra IDissues a token60 to 90 min lifeAOAI gatewayvalidates tokenchecks RBAC roleOpenAI User neededModeldeploymentasktokenallow
The token never leaves memory as a stored file. The gateway rejects the call if the identity lacks the role.
Who this is for: You have deployed a model in Azure AI Foundry (Part 3) and you can reach it with a key today. You want to move to identity-based auth and understand what encrypts your data. You do not need prior Entra or Key Vault experience; both are defined here on first use. Private networking is Part 9 and pairs with this.

Two ways to prove who you are

Every Azure OpenAI resource ships with two API keys, named key1 and key2. Two, so you can rotate one while the other keeps serving traffic. They are long strings that never expire on their own. Convenient on day one, a liability by month three, because they get pasted into notebooks, checked into a repo by accident, and shared in chat threads that outlive the project.

An Entra token is the opposite in every way that matters for security. It is short-lived, with a default lifetime somewhere between 60 and 90 minutes, after which it is useless and the client silently fetches another. It is tied to a specific identity, and it carries the role-based access control decisions attached to that identity. Your code asks Entra for a token scoped to cognitiveservices.azure.com, presents it, and the gateway decides based on the role, not on possession of a secret.

The security payoff shows up the moment a credential leaks. A stolen key works until a human notices and rotates it, which in practice can be hours or days. A stolen token stops working at its expiry, and if you turn on Continuous Access Evaluation, Entra can revoke it far sooner when it sees a risky sign-in. The chart below is the same leak, three different exposure windows.

How long a leaked credential stays usableLower is better. Minutes until the credential stops working0306090120hours to daysAPI keyuntil rotated75 minEntra tokenabout 5 minEntra plus CAE
The API key bar has no top. It runs until someone acts. The token expires on its own.
In practice: I still let developers use keys in a scratch subscription for a first ten-minute test, because the friction of setting up roles slows the very first call. Everything past that first call moves to Entra. The keys never make it into a pipeline variable or a shared config, which is where they turn into the credential nobody remembers issuing.

Which role grants what

Entra auth is only half the job. The token gets you in the door; the role decides which rooms you enter. This trips people up because being Owner or Contributor on the subscription does not, by itself, let you call the model. Those are management-plane roles. They let you create and delete the resource, read its keys, and change its settings, but the data plane, the inference API, is gated by a separate set of Cognitive Services roles you assign explicitly.

For an application that only needs to send prompts and get completions, assign Cognitive Services OpenAI User. That is the least-privilege role for inference. Reserve Cognitive Services OpenAI Contributor for identities that also create deployments, upload fine-tuning files, or manage Knowledge Base ingestion through the data plane. Keep the broad Cognitive Services Contributor role for platform automation that provisions resources, and do not attach it to a chat app.

RolePlaneGrantsAssign to
Cognitive Services OpenAI UserDataCall inference, read deploymentsYour app identity
Cognitive Services OpenAI ContributorDataInference plus create fine-tunes, upload filesBuild and training jobs
Cognitive Services ContributorManagementCreate, delete, configure the resourceProvisioning automation
OwnerManagementEverything above plus role assignmentA named human, sparingly

Management-plane roles do not grant data-plane access. An Owner who never assigned themselves OpenAI User will get a 403 on the inference API.

Managed identities, system-assigned or user-assigned?

A managed identity is an Entra identity that Azure creates and rotates for you, attached to a compute resource so your code never handles a secret. Your app running on a VM, a container app, or a function gets a token from the local identity endpoint, and you assign that identity the OpenAI User role. No client secret, no certificate, nothing to leak. This is the piece that makes keyless real rather than a slogan.

There are two kinds. A system-assigned managed identity is created with the resource and deleted with it, one to one, and it cannot be shared. A user-assigned managed identity is a standalone object with its own lifecycle that you can attach to many resources at once. For a single app, system-assigned is simplest. For a fleet, or when you want to grant the OpenAI User role once and reuse it across ten services, user-assigned saves you ten separate role assignments and the drift that comes with them.

One caveat worth flagging before you standardize on user-assigned everywhere. Some service-to-service flows, notably Azure OpenAI On Your Data reaching into Azure AI Search, have historically supported only the system-assigned identity on the Azure OpenAI resource. Check the current On Your Data docs for the flow you are wiring before you assume the user-assigned one will be accepted [VERIFY]. For the plain inference path in this part, either works, and DefaultAzureCredential picks up whichever is present.

Worked example

A platform team runs one chat API across three regions, so three container apps. With system-assigned identities that is three separate identities and three OpenAI User role assignments to keep in sync. Create one user-assigned identity instead, attach it to all three apps, assign OpenAI User once on the resource, and a fourth region later is one attach, zero new role assignments. When they retire a region, the identity and its role survive for the next one.

Turn off key access without locking yourself out

Assigning roles does not stop the keys from working. Both credential types stay valid side by side until you explicitly disable local authentication. You do that by setting the resource property disableLocalAuth to true, either in your ARM or Bicep template, through the Azure Policy named Foundry Tools resources should have key access disabled, or with a single CLI call. After it takes effect, Entra is the only way in, and every anonymous key is dead.

az resource update 
  --ids /subscriptions/<SUB>/resourceGroups/<RG>/providers/Microsoft.CognitiveServices/accounts/<NAME> 
  --set properties.disableLocalAuth=true

The order matters more than the command. Assign the OpenAI User role to your app identity first, confirm a real Entra call succeeds, and only then disable local auth. Flip that order and you take down production while you scramble to fix a role assignment. Keep a break-glass identity, a named admin with the role, out of band from the app so a bad deploy cannot orphan you.

Old keys can linger after you disable themGateway cache refresh window, minutes after the control-plane changeacceptedrejected060120mincutover happens somewhere in this band
The change is instant on the control plane. The gateway that enforces it can honor cached keys for minutes to hours.
Gotcha: Disabling local auth is a control-plane change that lands right away, but the shared gateway can keep accepting a previously valid key until its cached config refreshes, which can run from a few minutes to several hours. Do not treat the flip as instant proof the keys are dead. Verify with a real key call that you now get a 401 before you close the change ticket.
Disclaimer: Setting disableLocalAuth changes authentication on a live resource. Run it in a non-production resource first, confirm your Entra path works end to end, and keep a break-glass admin identity before you flip it in production.

How encryption at rest works here

Identity controls who calls the model. Encryption controls who can read the data the service stores. The good news is that you start protected. Every Azure OpenAI resource encrypts its data at rest by default with Microsoft-managed keys, using AES-256, with the key handling done entirely by the platform. There is no switch to flip and no cost line for it. Uploaded files, fine-tuning training data, and stored artifacts all land encrypted. In transit, calls to the endpoint use TLS 1.2 or higher.

For most workloads that is the correct and final answer. The keys are rotated and protected by Microsoft, and an auditor asking whether data is encrypted at rest gets a clean yes. You only move off the default when a specific control demands that your organization, not Microsoft, hold the key that can make the data readable, and can revoke it on demand. That control has a name and a real cost, which is the next section.

When customer-managed keys earn their place

Customer-managed keys, CMK, put your own key in front of the platform key. The service still encrypts data with a fast data-encryption key, then wraps that key with your key held in Azure Key Vault or Azure Key Vault Managed HSM. This is envelope encryption. The lever it gives you is revocation. Cut access to your key and the wrapped data-encryption key cannot be unwrapped, so the stored data becomes unreadable, even to the service. That is the whole reason CMK exists, and it is worth exactly as much as your requirement to hold that lever.

The setup has hard requirements. The key vault must have Soft Delete and Purge Protection enabled, so a deleted key cannot be permanently lost and take your data with it. The resource needs a managed identity granted permission to wrap and unwrap your key, through the Key Vault Crypto Service Encryption User role under RBAC or the wrapKey and unwrapKey permissions under a vault access policy [VERIFY exact role name]. Enabling CMK also switches on a system-assigned managed identity on the resource and registers it with Entra so it can reach the vault.

Now the cost, because it is the part teams skip until the invoice arrives. CMK backed by a standard Key Vault key adds only a few dollars a month in key operations. CMK backed by Managed HSM, which some regulated mandates require for a single-tenant hardware boundary, is billed per hour for the HSM pool and lands near 2,300 dollars a month before you have encrypted a single byte [VERIFY current Managed HSM rate]. Same revocation lever, two very different bills.

Monthly cost of the encryption choiceUS dollars per month, before data. The HSM boundary is the cost cliff06001200180024000 dollarsMS-managedabout 3CMK Key Vaultabout 2,300CMK Managed HSM
Standard Key Vault CMK is nearly free. Managed HSM is a step change you take only when a mandate names it.
OptionWho holds the keyYou can revokeSetup and cost
Microsoft-managed keysMicrosoftNoNone, included
CMK in Key VaultYouYesVault plus role, a few dollars
CMK in Managed HSMYou, single-tenant HSMYesHSM pool, hundreds to thousands

Revocation is the only column that justifies leaving the default. If you cannot name a rule that needs it, you do not need it.

My take: I default to Microsoft-managed keys and treat CMK as a compliance answer, not a security upgrade. When a customer does need CMK, standard Key Vault covers almost every case at a few dollars a month. I have wired Managed HSM exactly for the handful of mandates that name a single-tenant hardware boundary in writing, and never as a nice-to-have, because the 2,300 dollar floor buys nothing extra unless that clause exists.

A keyless call from a Python service

Here is the whole pattern in one file. The azure-identity library builds a token provider from the ambient identity, and the OpenAI client asks it for a fresh token whenever the old one nears expiry. There is no key in the code, no key in an environment variable, no key anywhere. DefaultAzureCredential resolves your identity from the environment, a managed identity in Azure or your az login locally, and if you attached a user-assigned identity you point at it with the AZURE_CLIENT_ID variable.

from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AzureOpenAI

# No key anywhere. The workload identity supplies a short-lived token.
token_provider = get_bearer_token_provider(
    DefaultAzureCredential(),
    "https://cognitiveservices.azure.com/.default",
)

client = AzureOpenAI(
    azure_endpoint="https://my-aoai.openai.azure.com",
    azure_ad_token_provider=token_provider,
    api_version="2024-10-21",
)

resp = client.chat.completions.create(
    model="gpt-4o",  # your deployment name, not the base model
    messages=[{"role": "user", "content": "ping"}],
)
print(resp.choices[0].message.content)

Expected output: the model reply printed to stdout, a short string such as pong or a greeting. If you see text, the identity, the role, and the endpoint all lined up.

Failure mode: a 401 means no valid token was obtained, so DefaultAzureCredential found no identity, or you are local and never ran az login. A 403 means the token is valid but the identity lacks Cognitive Services OpenAI User on this resource, which no code change fixes, only a role assignment does. A DeploymentNotFound means you passed the base model name instead of your deployment name in the model field.

Managed identity first, keys behind a break-glass

My recommendation is one line. Give every workload a managed identity, assign it Cognitive Services OpenAI User, run the keyless pattern above, and disable local auth only after a real Entra call has succeeded. Keep one break-glass admin identity out of band so a bad deploy cannot lock you out. On encryption, stay on Microsoft-managed keys unless a written policy names key revocation, use standard Key Vault CMK when it does, and reserve Managed HSM for the rare mandate that spells out a single-tenant hardware boundary and its bill.

You now have who can call the model and who can read its data. The next question is what you actually send, which is the request shape itself, covered in Part 11 on calling models with REST, the SDKs, and the Responses API. If you also run AWS, the parallel key-control story lives in the Bedrock data residency and KMS part. Before you move on, assign OpenAI User to one app identity and make a keyless call today.

Azure Gen AI Series · Part 10 of 30
« Previous: Part 9  |  Guide  |  Next: Part 11 »

References

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading