Managed Identity: Zero Credentials in Code

by Alien Brain Trust AI Learning
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:

  1. Azure Static Web App has Managed Identity enabled
  2. Azure assigns it an identity (like a service account)
  3. Key Vault has access policy granting that identity “Get” and “List” permissions
  4. When the function runs, it authenticates using the Managed Identity
  5. 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:

  1. Environment variables (service principal credentials)
  2. Managed Identity (system-assigned or user-assigned)
  3. Azure CLI (if you’re logged in locally)
  4. Azure PowerShell (if you’re logged in)
  5. 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:

  1. Go to your Static Web App
  2. Click “Identity” in left menu
  3. 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:

  1. Go to Key Vault (alienbraintrust-kv)
  2. Click “Access policies” in left menu
  3. Click ”+ Create”
  4. Permissions:
    • Secret permissions: “Get” and “List”
  5. Click “Next”
  6. Principal:
    • Search for your Static Web App name: learn-labs-blog
    • Select it
  7. 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 login to 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_URL environment 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:

  1. Enable Managed Identity on your Azure resource
  2. Grant it access to other Azure resources
  3. Use DefaultAzureCredential in code
  4. 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.