Cómo usar Managed Identity con FastAPI en Azure
Guía práctica para usar Azure Managed Identity en un backend FastAPI, eliminando secretos de la aplicación por completo.
El problema
Si alguna vez pusiste un connection string de Azure Storage directo en tu código o en un .env que terminó en el repo,
este post es para vos.
Managed Identity es el mecanismo de Azure para que una aplicación se autentique con otros servicios sin credenciales hardcodeadas. Tu App Service, Container App o Function obtiene una identidad en Azure AD y con esa identidad accede a Key Vault, Storage, Service Bus, etc.
Cómo funciona
App Service (con Managed Identity)
│
│ "Soy el app service X, quiero un token para Storage"
▼
Azure AD (verifica identidad del recurso)
│
│ Token JWT válido
▼
Azure Blob Storage (valida el token, autoriza la operación)
No hay contraseñas. No hay tokens que rotar. No hay secretos en variables de entorno (en teoría).
Setup en Azure
Primero, habilitar la Managed Identity en el App Service:
az webapp identity assign \
--name my-fastapi-app \
--resource-group my-rg
Eso devuelve un principalId. Ahora darle acceso al Storage:
az role assignment create \
--assignee <principalId> \
--role "Storage Blob Data Contributor" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<storage>
FastAPI: código real
Instalar las dependencias:
pip install azure-identity azure-storage-blob
Ahora el cliente que funciona tanto local (con tu usuario de Azure CLI) como en producción (con Managed Identity):
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
# DefaultAzureCredential prueba en orden:
# 1. Environment variables (desarrollo local con service principal)
# 2. Workload Identity (Kubernetes)
# 3. Managed Identity (Azure App Service, Container Apps, etc.)
# 4. Azure CLI (tu máquina local)
# 5. Visual Studio Code, etc.
def get_blob_client(account_url: str) -> BlobServiceClient:
credential = DefaultAzureCredential()
return BlobServiceClient(account_url=account_url, credential=credential)
En tu app FastAPI podés usar dependency injection:
from functools import lru_cache
from fastapi import Depends
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
import os
STORAGE_ACCOUNT_URL = os.environ["AZURE_STORAGE_ACCOUNT_URL"]
@lru_cache
def get_credential() -> DefaultAzureCredential:
return DefaultAzureCredential()
def get_blob_service(
credential: DefaultAzureCredential = Depends(get_credential),
) -> BlobServiceClient:
return BlobServiceClient(
account_url=STORAGE_ACCOUNT_URL,
credential=credential
)
# En tu router
from fastapi import APIRouter
router = APIRouter()
@router.get("/files/{blob_name}")
async def download_file(
blob_name: str,
blob_service: BlobServiceClient = Depends(get_blob_service),
):
container = blob_service.get_container_client("my-container")
blob = container.get_blob_client(blob_name)
data = blob.download_blob().readall()
return {"size": len(data)}
Desarrollo local
Para que funcione en tu máquina sin Managed Identity:
# Login con Azure CLI
az login
# Setear el tenant si tenés varios
az account set --subscription <sub-id>
DefaultAzureCredential va a usar tu sesión de CLI automáticamente. No necesitás ninguna variable de entorno extra.
Qué evitar
- ❌ No uses
ManagedIdentityCredentialdirectamente — hace difícil el desarrollo local - ❌ No guardes el
client_secretde un Service Principal en el código - ❌ No pongas connection strings en variables de entorno cuando podés usar Managed Identity
- ✅ Usá
DefaultAzureCredentialsiempre — funciona en todos los contextos
Key Vault también
Si necesitás secretos que no tienen RBAC nativo de Azure (ej: una API key de terceros), ponelos en Key Vault y accedé con Managed Identity:
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
KEY_VAULT_URL = os.environ["AZURE_KEY_VAULT_URL"]
def get_secret(secret_name: str) -> str:
credential = DefaultAzureCredential()
client = SecretClient(vault_url=KEY_VAULT_URL, credential=credential)
return client.get_secret(secret_name).value
Con esto eliminás la necesidad de tener ningún secreto en el código o en las variables de entorno de la aplicación.