Managed Identity: Zero Credentials in Code
Managed Identity: Zero Credentials in Code
Meta Description: How Azure Managed Identity eliminates API keys, passwords, and secrets from your code entirely. DefaultAzureCredential does it all.
The Learn Labs enrollment system has zero credentials in code. No API keys. No passwords. No connection strings. The Azure Function authenticates to Key Vault without any secrets.
Here’s how.
The Traditional Approach (What We Avoided)
Standard pattern for accessing Azure Key Vault:
// ❌ Traditional: Service Principal with credentials
const { ClientSecretCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");
const credential = new ClientSecretCredential(
"tenant-id-goes-here", // Stored where?
"client-id-goes-here", // Stored where?
"client-secret-goes-here" // Stored WHERE?
);
const client = new SecretClient(keyVaultUrl, credential);
const secret = await client.getSecret("airtable-api-key");
The problem: You need credentials to access credentials. Where do you store the service principal client secret? In environment variables? You’ve just moved the problem.
The Managed Identity Approach (What We Used)
Our enrollment system:
// ✅ Managed Identity: Zero credentials
const { DefaultAzureCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");
const credential = new DefaultAzureCredential();
const client = new SecretClient(keyVaultUrl, credential);
const secret = await client.getSecret("airtable-api-key");
No tenant ID. No client ID. No client secret. DefaultAzureCredential figures out authentication automatically.
How it works:
- Azure Static Web App has Managed Identity enabled
- Azure assigns it an identity (like a service account)
- Key Vault has access policy granting that identity “Get” and “List” permissions
- When the function runs, it authenticates using the Managed Identity
- Key Vault validates the identity and returns the secret
All authentication happens at the Azure platform level. Your code never touches credentials.
What is Managed Identity?
Managed Identity is Azure’s way of giving resources their own identity.
Think of it like this:
- A user has an identity (email, password, MFA)
- An app registration has an identity (client ID, client secret)
- A Managed Identity resource has an identity (no credentials needed)
Types:
System-Assigned Managed Identity
- Created automatically when you enable it on a resource
- Tied to the resource lifecycle (deleted when resource is deleted)
- One identity per resource
- Use case: Single app needs access to specific resources
User-Assigned Managed Identity
- Created as a standalone Azure resource
- Can be assigned to multiple resources
- Persists after resource deletion
- Use case: Multiple apps need the same set of permissions
We used System-Assigned for the Learn Labs enrollment system because:
- Simple setup (toggle in Azure Portal)
- Identity lifecycle matches the app
- No need to manage separate identity resource
How DefaultAzureCredential Works
DefaultAzureCredential tries multiple authentication methods in order:
Authentication chain:
- Environment variables (service principal credentials)
- Managed Identity (system-assigned or user-assigned)
- Azure CLI (if you’re logged in locally)
- Azure PowerShell (if you’re logged in)
- Interactive browser (prompts for login)
In production: Managed Identity succeeds. No other methods are tried.
In local development: Environment variables or Azure CLI succeeds (you’re logged in locally).
Why this is powerful: Same code works in production (Managed Identity) and local development (Azure CLI). No environment-specific credential handling.
Step-by-Step Setup
1. Enable Managed Identity on Azure Static Web App
Via Azure Portal:
- Go to your Static Web App
- Click “Identity” in left menu
- Under “System assigned” tab:
- Toggle Status to On
- Click “Save”
- Click “Yes” to confirm
Result: Azure creates a Managed Identity and shows you the Object ID.
Via Azure CLI:
az staticwebapp identity assign \
--name learn-labs-blog \
--resource-group learn-labs-rg
What happened: Azure registered your app as a security principal in Azure Active Directory. It now has an identity that can be granted permissions.
2. Grant Key Vault Access
Via Azure Portal:
- Go to Key Vault (
alienbraintrust-kv) - Click “Access policies” in left menu
- Click ”+ Create”
- Permissions:
- Secret permissions: “Get” and “List”
- Click “Next”
- Principal:
- Search for your Static Web App name:
learn-labs-blog - Select it
- Search for your Static Web App name:
- Click “Next” → “Create”
Via Azure CLI:
# Get the Static Web App's principal ID
PRINCIPAL_ID=$(az staticwebapp show \
--name learn-labs-blog \
--resource-group learn-labs-rg \
--query identity.principalId -o tsv)
# Grant access
az keyvault set-policy \
--name alienbraintrust-kv \
--object-id $PRINCIPAL_ID \
--secret-permissions get list
What happened: Key Vault now trusts your Static Web App’s Managed Identity. When the app requests secrets, Key Vault validates the identity and grants access.
3. Use DefaultAzureCredential in Code
Install SDK:
npm install @azure/identity @azure/keyvault-secrets
Implementation:
const { DefaultAzureCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");
const keyVaultUrl = process.env.KEY_VAULT_URL;
const credential = new DefaultAzureCredential();
const client = new SecretClient(keyVaultUrl, credential);
// Fetch secrets
const airtableApiKey = await client.getSecret("airtable-api-key");
const airtableBaseId = await client.getSecret("airtable-base-id");
const githubToken = await client.getSecret("github-token");
// Use the secret values
const apiKey = airtableApiKey.value;
const baseId = airtableBaseId.value;
const token = githubToken.value;
Only environment variable needed: KEY_VAULT_URL
This is not a secret. It’s a public URL. Anyone can know it. Without proper access policies, they can’t read your secrets.
4. Test Locally
Authenticate with Azure CLI:
az login
Run your function:
npm start
DefaultAzureCredential will use your Azure CLI login. If your account has access to Key Vault, secrets fetch works locally.
No local secrets needed. Your local development uses your Azure identity, not service principal credentials.
Security Benefits
1. No Credentials in Code
Problem: Hardcoded secrets get committed to Git Solution: Zero credentials in code to commit
2. No Credentials in Environment Variables
Problem: Environment variables leak via logs, error messages, or process dumps Solution: Only Key Vault URL in environment (not a secret)
3. No Credential Rotation for App Identity
Problem: Service principal client secrets expire Solution: Managed Identity credentials are managed by Azure (automatic rotation)
4. Principle of Least Privilege
Problem: Shared credentials give too much access Solution: Each app has its own identity with specific permissions
5. Audit Trail
Problem: Can’t track who accessed what Solution: Every access logged with the identity used
Common Patterns
Pattern 1: Key Vault Access
// ✅ Managed Identity
const credential = new DefaultAzureCredential();
const client = new SecretClient(keyVaultUrl, credential);
Pattern 2: Azure Storage Access
// ✅ Managed Identity
const { DefaultAzureCredential } = require("@azure/identity");
const { BlobServiceClient } = require("@azure/storage-blob");
const credential = new DefaultAzureCredential();
const blobClient = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net`,
credential
);
Pattern 3: Azure SQL Database
// ✅ Managed Identity
const { DefaultAzureCredential } = require("@azure/identity");
const sql = require("mssql");
const credential = new DefaultAzureCredential();
const token = await credential.getToken("https://database.windows.net/");
const config = {
server: "your-server.database.windows.net",
database: "your-database",
authentication: {
type: "azure-active-directory-access-token",
options: {
token: token.token
}
}
};
const pool = await sql.connect(config);
Pattern 4: Azure Service Bus
// ✅ Managed Identity
const { DefaultAzureCredential } = require("@azure/identity");
const { ServiceBusClient } = require("@azure/service-bus");
const credential = new DefaultAzureCredential();
const client = new ServiceBusClient(
"your-namespace.servicebus.windows.net",
credential
);
The pattern: Every Azure SDK supports DefaultAzureCredential. Authentication is consistent across services.
Local Development Workflow
Option 1: Azure CLI (Recommended)
az login
npm start
Your local app uses your Azure identity. Works exactly like production.
Option 2: Environment Variables (Fallback)
# .env file
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
DefaultAzureCredential will use these if Azure CLI isn’t available.
Option 3: Service Principal (Team Development) Create a service principal for local development:
az ad sp create-for-rbac --name "learn-labs-local-dev"
Grant it Key Vault access. Share credentials with team via secure method (1Password, LastPass, etc.).
We use Option 1 (Azure CLI) for solo development. Each developer logs in with their own account. No shared credentials.
Troubleshooting
Error: “Failed to retrieve token from the managed identity endpoint”
Cause: Managed Identity not enabled or not running on Azure
Fix:
- Verify Managed Identity is enabled (Azure Portal → Identity)
- Check you’re running on Azure (not locally)
- If local, run
az loginto authenticate
Error: “Forbidden” or “Access Denied”
Cause: Missing access policy on Key Vault
Fix:
- Go to Key Vault → Access policies
- Verify your app’s Managed Identity has “Get” and “List” permissions
- If missing, add the policy (Step 2 above)
Error: “The specified resource does not exist”
Cause: Wrong Key Vault URL or secret name
Fix:
- Verify
KEY_VAULT_URLenvironment variable - Verify secret name matches exactly (case-sensitive)
- Check Key Vault exists and you have access
Logs Show “Trying multiple authentication methods”
Cause: DefaultAzureCredential is working correctly, trying auth chain
Fix: No fix needed. This is normal. If Managed Identity succeeds, later methods aren’t tried.
Cost Implications
Managed Identity itself: Free Key Vault operations: $0.03 per 10,000 operations (after 10k free tier)
Our usage:
- 3 secrets × 150 enrollments/month = 450 operations
- With 5-minute cache: ~4,320 operations/month worst case
- Cost: $0 (within 10k free tier)
Compared to environment variables: $0 vs $0
The cost is the same. The security and management benefits are free.
When to Use Managed Identity
Use Managed Identity when:
- Accessing Azure resources (Key Vault, Storage, SQL, Service Bus)
- Running on Azure (Functions, App Service, VMs, AKS)
- Security or compliance matters
- Multiple team members work on the project
- You need audit trails
Don’t use Managed Identity when:
- Accessing non-Azure services (GitHub API, Stripe API, etc.)
- Running outside Azure (AWS, GCP, on-premises)
- It’s a quick prototype you’ll throw away
- The service doesn’t support Azure AD authentication
For non-Azure services: Store API keys in Key Vault, use Managed Identity to fetch them. You still get audit logs, expiration tracking, and centralized rotation.
The Learn Labs Implementation
Our Azure Function:
async function getSecrets(context) {
const keyVaultUrl = process.env.KEY_VAULT_URL;
const credential = new DefaultAzureCredential();
const client = new SecretClient(keyVaultUrl, credential);
const [airtableApiKey, airtableBaseId, githubToken] = await Promise.all([
client.getSecret("airtable-api-key"),
client.getSecret("airtable-base-id"),
client.getSecret("github-token")
]);
return {
AIRTABLE_API_KEY: airtableApiKey.value,
AIRTABLE_BASE_ID: airtableBaseId.value,
GITHUB_TOKEN: githubToken.value
};
}
Credentials in this code: Zero
Environment variables needed: One (KEY_VAULT_URL, which is public)
Secrets exposed if code leaks: Zero
Comparison: Before and After
Before (Service Principal)
// ❌ Credentials in environment variables
const credential = new ClientSecretCredential(
process.env.AZURE_TENANT_ID, // Secret
process.env.AZURE_CLIENT_ID, // Secret
process.env.AZURE_CLIENT_SECRET // Secret
);
Secrets in environment: 3 Rotation required: Every 90 days (client secret expiry) Leaked in logs: Possible
After (Managed Identity)
// ✅ No credentials
const credential = new DefaultAzureCredential();
Secrets in environment: 0 Rotation required: Never (Azure manages it) Leaked in logs: Impossible
Takeaway
Managed Identity eliminates an entire category of security issues. No credentials to leak. No secrets to rotate. No environment variables to secure.
The pattern:
- Enable Managed Identity on your Azure resource
- Grant it access to other Azure resources
- Use
DefaultAzureCredentialin code - Done
15 lines of setup, zero ongoing maintenance.
For the Learn Labs enrollment system, this means:
- Key Vault access with zero credentials
- Audit trail of all secret access
- No service principal rotation
- Same code works locally and in production
If you’re on Azure, there’s no reason not to use Managed Identity.
Related: In the next post, we’ll break down the free tier math that lets us run enterprise infrastructure for $0/month with room to scale 100x.
Try it: Enable Managed Identity on your next Azure project. You’ll never go back to service principals.