Most teams running workloads on AKS need to access Azure resources — Key Vault, Storage, Service Bus, SQL. The common shortcut is to create a service principal, export a client secret, and shove it into a Kubernetes secret. It works, but it introduces credentials that expire, need rotation, and can leak.

Microsoft Entra Workload Identity eliminates all of that. Your pod gets a short-lived, automatically rotated token — no secrets stored anywhere. Under the hood it uses OIDC federation: AKS acts as an identity provider, Entra ID trusts it, and your pod exchanges a projected service account token for an Azure access token at runtime.

This guide walks through the full setup — enabling the feature on AKS, wiring up a managed identity, and deploying a workload that reads from Azure Key Vault without a single stored credential.

How It Works

The flow is straightforward:

  1. AKS exposes an OIDC issuer URL — a public endpoint that signs Kubernetes service account tokens
  2. You create an Azure User-Assigned Managed Identity and a federated credential that says “trust tokens from this AKS cluster issued for this service account”
  3. Your pod’s service account is annotated with the managed identity’s client ID
  4. The Workload Identity webhook injects two things into your pod: a projected token volume and environment variables (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE)
  5. Azure SDK automatically picks up these variables and exchanges the token for an access token — zero code changes needed

Prerequisites

Step 1 — Enable OIDC Issuer and Workload Identity on AKS

If you’re creating a new cluster:

az aks create \
  --resource-group <rg> \
  --name <cluster> \
  --enable-oidc-issuer \
  --enable-workload-identity \
  --generate-ssh-keys

For an existing cluster:

az aks update \
  --resource-group <rg> \
  --name <cluster> \
  --enable-oidc-issuer \
  --enable-workload-identity

Retrieve and store the OIDC issuer URL — you’ll need it when creating the federated credential:

OIDC_ISSUER=$(az aks show \
  --resource-group <rg> \
  --name <cluster> \
  --query "oidcIssuerProfile.issuerUrl" -o tsv)

echo $OIDC_ISSUER
# https://<region>.oic.prod-aks.azure.com/<tenant-id>/<cluster-id>/

Step 2 — Create a User-Assigned Managed Identity

az identity create \
  --resource-group <rg> \
  --name nerdsolv-workload-identity

# Store the client ID and principal ID for later steps
CLIENT_ID=$(az identity show \
  --resource-group <rg> \
  --name nerdsolv-workload-identity \
  --query clientId -o tsv)

PRINCIPAL_ID=$(az identity show \
  --resource-group <rg> \
  --name nerdsolv-workload-identity \
  --query principalId -o tsv)

Step 3 — Create a Kubernetes Service Account

Create the namespace and service account. The annotation links the service account to the managed identity.

kubectl create namespace app-workloads
# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nerdsolv-sa
  namespace: app-workloads
  annotations:
    azure.workload.identity/client-id: "<CLIENT_ID>"
  labels:
    azure.workload.identity/use: "true"
kubectl apply -f serviceaccount.yaml

Step 4 — Create the Federated Credential

This tells Entra ID: “trust tokens issued by this AKS OIDC endpoint for the nerdsolv-sa service account in the app-workloads namespace.”

az identity federated-credential create \
  --name nerdsolv-fed-credential \
  --identity-name nerdsolv-workload-identity \
  --resource-group <rg> \
  --issuer $OIDC_ISSUER \
  --subject "system:serviceaccount:app-workloads:nerdsolv-sa" \
  --audiences api://AzureADTokenExchange

Subject format: system:serviceaccount:<namespace>:<service-account-name> — must match exactly what’s in Kubernetes.

Step 5 — Grant the Identity Access to Azure Resources

For this example we’ll give the identity read access to an Azure Key Vault.

KEY_VAULT_ID=$(az keyvault show \
  --name <your-keyvault> \
  --resource-group <rg> \
  --query id -o tsv)

az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee-object-id $PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --scope $KEY_VAULT_ID

Use Key Vault Secrets User for read-only access. Use Key Vault Secrets Officer if the workload needs to write secrets.

Step 6 — Deploy a Workload

The key requirement on the pod: set azure.workload.identity/use: "true" on the pod spec and reference the annotated service account. The webhook handles the rest.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nerdsolv-app
  namespace: app-workloads
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nerdsolv-app
  template:
    metadata:
      labels:
        app: nerdsolv-app
        azure.workload.identity/use: "true"   # required
    spec:
      serviceAccountName: nerdsolv-sa          # the annotated SA
      containers:
        - name: app
          image: mcr.microsoft.com/azure-cli:latest
          command: ["sleep", "infinity"]
          env:
            - name: KEYVAULT_URL
              value: "https://<your-keyvault>.vault.azure.net"
kubectl apply -f deployment.yaml

Verify the webhook injected the identity environment variables:

kubectl exec -n app-workloads deploy/nerdsolv-app -- env | grep AZURE
# AZURE_CLIENT_ID=<client-id>
# AZURE_TENANT_ID=<tenant-id>
# AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token
# AZURE_AUTHORITY_HOST=https://login.microsoftonline.com/

Test Key Vault access from inside the pod:

kubectl exec -n app-workloads deploy/nerdsolv-app -- \
  az keyvault secret show \
  --vault-name <your-keyvault> \
  --name my-secret \
  --query value -o tsv

No login, no credentials — it just works.

Step 7 — Using the Identity in Application Code

Any Azure SDK that supports DefaultAzureCredential picks up the injected variables automatically. No code changes needed when moving between local development (where you use az login) and the cluster (where Workload Identity takes over).

Python:

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

credential = DefaultAzureCredential()
client = SecretClient(vault_url=os.environ["KEYVAULT_URL"], credential=credential)
secret = client.get_secret("my-secret")

Node.js:

import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";

const credential = new DefaultAzureCredential();
const client = new SecretClient(process.env.KEYVAULT_URL, credential);
const secret = await client.getSecret("my-secret");

Go:

cred, _ := azidentity.NewDefaultAzureCredential(nil)
client, _ := azsecrets.NewClient(os.Getenv("KEYVAULT_URL"), cred, nil)
resp, _ := client.GetSecret(ctx, "my-secret", "", nil)

Troubleshooting

Pod not getting AZURE_ environment variables*

Check the webhook is running:

kubectl get pods -n kube-system | grep azure-wi-webhook

If missing, the cluster may not have --enable-workload-identity applied — run the az aks update command from Step 1.

Token exchange fails with AADSTS70021

The federated credential subject doesn’t match. Double-check: namespace and service account name in the --subject flag must match exactly what’s in Kubernetes.

az identity federated-credential create returns 409

A federated credential with that name already exists. Either delete it first or use a different name.

Summary

Workload Identity is the right way to give AKS workloads access to Azure resources. The setup is a one-time five-step process per identity: enable OIDC on AKS, create a managed identity, annotate a service account, create a federated credential, assign RBAC. After that, any pod using that service account gets automatic, secretless Azure authentication — and any Azure SDK using DefaultAzureCredential just works.

No secrets to rotate. No credentials to leak. No expiry surprises at 2am.

Was this article helpful?

Need help setting this up for your team?

nerdSolv designs and deploys cloud-native infrastructure on Azure, AWS, and GCP.

Talk to us →