Like many Azure APIs, the Azure OpenAI service gives developers the option to authenticate with either API keys or keyless authentication (via Entra identity). Since it's a security best practice to avoid keys whenever possible, we're hoping to make it easy for developers to move to keyless OpenAI authentication by walking through all the necessary steps in this blog post. We've also made a new template for keyless deployment if you want to get started immediately.
But first, let's talk about the downsides of API keys. It's tempting to use keys, since the setup looks so straightforward - you only need an endpoint URL and key:
client = openai.AzureOpenAI(
api_version="2024-02-15-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_KEY")
)
But using API keys in a codebase can lead to all kinds of issues. To name a few:
getenv()
call with a hardcoded string, or a developer who adds a .env
file to a commit.I've seen all of these situations play out, and I don't want them to happen to other developers. A more secure approach is to use authentication tokens, since they are acquired dynamically, have a limited lifespan, and aren't stored in plaintext.
This code authenticates to Azure OpenAI with the openai Python package and Azure Python SDK:
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
azure_credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(azure_credential,
"https://cognitiveservices.azure.com/.default")
client = AzureOpenAI(
api_version="2024-02-15-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
azure_ad_token_provider=token_provider
)
The differences:
DefaultAzureCredential
, which will iterate through many possible credential types until it finds a valid Azure login.azure_ad_token_provider
. The SDK will use that token provider to fetch access tokens when necessary, and even take care of refreshing the token for us.The next step is to make sure that whoever is running the code has permission to access the OpenAI service. By default, you will not have permission, even if you created the OpenAI service yourself. That's a security measure to make sure you don't accidentally access production resources from a local machine (particularly helpful when your code deals with write operations on databases).
To access an OpenAI resource, you need the "Cognitive Services OpenAI User" role (role ID '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'). That can be assigned using the Azure Portal, Azure CLI, or ARM/Bicep.
First, set the following environment variables:
PRINCIPAL_ID
: The principal ID of your logged in account. You can get that with the Azure CLI by running az ad signed-in-user show --query id -o tsv
or you can open the Azure Portal, search for "Microsoft Entra ID", select the Users tab, filter for your account, and copy the "object ID" under your email address.SUBSCRIPTION_ID
: The subscription ID of your logged in account. You can see that on the Overview page of your Azure OpenAI resource in the Azure Portal.RESOURCE_GROUP
: The resource group of the OpenAI resource.Then run this command using the Azure CLI:
az role assignment create \
--role "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd" \
--assignee-object-id "$PRINCIPAL_ID" \
--scope /subscriptions/"$SUBSCRIPTION_ID"/resourceGroups/"$RESOURCE_GROUP" \
--assignee-principal-type User
We use the Azure Developer CLI to deploy all of our samples, which relies on Bicep files to declare the infrastructure-as-code. That results in more repeatable deploys, so it's a great approach for deploying production applications.
This Bicep resource creates the role, assuming a principalId
parameter is set:
resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().id, resourceGroup().id,
principalId, roleDefinitionId)
properties: {
principalId: principalId
principalType: 'User'
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions',
'5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')
}
}
You can also see how this sample's main.bicep uses a module to set up the role.
If you are unable to use those automated approaches (preferred), it's also possible to use the Azure Portal to create the role:
The next step is to ensure your deployed application can also use a DefaultAzureCredential
token to access the OpenAI resource. That requires setting up a Managed Identity and assigning that same role to the Managed identity. There are two kinds of managed identities: system-assigned and user-assigned. All Azure hosting platforms support managed identity. We'll start with App Service and system-assigned identities as an example.
This is how we create an App Service with a system-assigned identity in Bicep code:
resource appService 'Microsoft.Web/sites@2022-03-01' = {
name: name
location: location
identity: { type: 'SystemAssigned'}
...
}
For more details, see this article on Managed Identity for App Service.
The role assignment process is largely the same for the host as it was for a user, but the principal ID must be set to the managed identity's principal ID instead and the principal type is "ServicePrincipal".
For example, this Bicep assigns the role for an App Service system-assigned identity:
resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().id, resourceGroup().id,
principalId, roleDefinitionId)
properties: {
principalId: appService.identity.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions',
'5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')
}
}
It's also possible to use a system-assigned identity for Azure Container Apps, using a similar approach as above. However, for our samples, we needed to use user-assigned identities so that we could give the same identity access to Azure Container Registry before the ACA app was provisioned. That's the advantage of a user-assigned identity: reuse across multiple Azure resources.
First, we create a new identity outside of the container app Bicep resource:
resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: '${prefix}-id-aca'
location: location
}
Then we assign that identity to the container app Bicep resource:
resource app 'Microsoft.App/containerApps@2022-03-01' = {
name: name
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: { '${userIdentity.id}': {} }
}
...
When using a user-assigned identity, we need to modify our call to AzureDefaultCredential
to tell it which identity to use, since you could potentially have multiple user-assigned identities (not just the single system-assigned identity for the hosting environment).
The following code retrieves the identity's ID from the environment variables and specifies it as the client_id
for the Managed Identity credential:
default_credential = azure.identity.ManagedIdentityCredential(
client_id=os.getenv("AZURE_OPENAI_CLIENT_ID"))
At this point, you should be able to access OpenAI both for local development and in production. Unless, that is, you're developing with a local Docker container. By default, a Docker container does not have a way to access any of your local credentials, so you'll see authentication errors in the logs. It used to be possible to use a workaround with volumes to access the credential, but after Azure started encrypting the local credential, it's now an open question as to how to easily authenticate inside a local container.
What are our options?
As you can see, it's not entirely straightforward to authenticate to OpenAI without keys, depending on how you're developing locally and where you're deploying.
The following code uses a key when it's set in the environment, uses a user-assigned Managed Identity when the identity ID is set in the environment, and otherwises uses DefaultAzureCredential
:
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
from azure.identity import get_bearer_token_provider
client_args = {}
if os.getenv("AZURE_OPENAI_KEY"):
client_args["api_key"] = os.getenv("AZURE_OPENAI_KEY")
else:
if client_id := os.getenv("AZURE_OPENAI_CLIENT_ID"):
# Authenticate using a user-assigned managed identity on Azure
azure_credential = ManagedIdentityCredential(
client_id=client_id)
else:
# Authenticate using the default Azure credential chain
azure_credential = DefaultAzureCredential()
client_args["azure_ad_token_provider"] = get_bearer_token_provider(
azure_credential, "https://cognitiveservices.azure.com/.default")
openai_client = openai.AsyncAzureOpenAI(
api_version=os.getenv("AZURE_OPENAI_API_VERSION") or "2024-02-15-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
**client_args,
)
Here are more examples to help you move to keyless authentication for your OpenAI projects:
azd
to provision the OpenAI and RBAC role for a local user account only.azd
to provision the OpenAI and RBAC role for both local user account and Azure Container App user-assigned identity.azd
to provisions the OpenAI and RBAC role for both local user account and App Service system identity.You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.