Back to Blog
4 min read

Keyless Authentication: The Future of Cloud Security

Keyless authentication eliminates the need for stored credentials by using identity federation, managed identities, and short-lived tokens. This is the foundation of modern zero-trust security.

The Keyless Architecture

┌─────────────────────────────────────────────────────┐
│                  No Stored Secrets                   │
├─────────────────────────────────────────────────────┤
│  Managed Identities    │  Workload Identity         │
│  (Azure Resources)     │  Federation (External)     │
├─────────────────────────────────────────────────────┤
│  Short-lived Tokens    │  Just-in-Time Access       │
└─────────────────────────────────────────────────────┘

Implementing Keyless in Azure

Service-to-Service Communication

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Storage.Blobs;
using Microsoft.Data.SqlClient;

public class KeylessService
{
    private readonly DefaultAzureCredential _credential;

    public KeylessService()
    {
        // Single credential for all Azure services
        _credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
        {
            ExcludeEnvironmentCredential = true,
            ExcludeAzureCliCredential = false,  // For local development
            ManagedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")
        });
    }

    public async Task<string> GetSecretAsync(string vaultUri, string secretName)
    {
        var client = new SecretClient(new Uri(vaultUri), _credential);
        var secret = await client.GetSecretAsync(secretName);
        return secret.Value.Value;
    }

    public async Task<BlobContainerClient> GetBlobContainerAsync(string storageUri, string containerName)
    {
        var client = new BlobServiceClient(new Uri(storageUri), _credential);
        return client.GetBlobContainerClient(containerName);
    }

    public async Task<SqlConnection> GetDatabaseConnectionAsync(string server, string database)
    {
        var connectionString = $"Server={server};Database={database};";
        var connection = new SqlConnection(connectionString);

        // Get token for SQL Database
        var token = await _credential.GetTokenAsync(
            new Azure.Core.TokenRequestContext(new[] { "https://database.windows.net/.default" }));

        connection.AccessToken = token.Token;
        return connection;
    }
}

Kubernetes Workload Identity

# kubernetes/deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: keyless-app-sa
  annotations:
    azure.workload.identity/client-id: "$CLIENT_ID"
  labels:
    azure.workload.identity/use: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keyless-app
spec:
  template:
    metadata:
      labels:
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: keyless-app-sa
      containers:
        - name: app
          image: myregistry.azurecr.io/keyless-app:latest
          env:
            - name: AZURE_TENANT_ID
              value: "$TENANT_ID"
            - name: AZURE_CLIENT_ID
              value: "$CLIENT_ID"
            - name: STORAGE_ACCOUNT
              value: "https://mystorage.blob.core.windows.net"
            - name: KEY_VAULT_URI
              value: "https://mykeyvault.vault.azure.net"

Azure Functions with Keyless Bindings

// host.json
{
  "version": "2.0",
  "extensions": {
    "blobs": {
      "maxDegreeOfParallelism": 4
    }
  }
}
// local.settings.json
{
  "Values": {
    "AzureWebJobsStorage__accountName": "mystorage",
    "ServiceBusConnection__fullyQualifiedNamespace": "myservicebus.servicebus.windows.net",
    "CosmosDBConnection__accountEndpoint": "https://mycosmos.documents.azure.com:443/"
  }
}
public class KeylessFunctions
{
    [FunctionName("ProcessBlob")]
    public async Task ProcessBlob(
        [BlobTrigger("input/{name}", Connection = "AzureWebJobsStorage")] Stream blob,
        [Blob("output/{name}", FileAccess.Write, Connection = "AzureWebJobsStorage")] Stream output,
        ILogger log)
    {
        // Managed identity used automatically for blob access
        await blob.CopyToAsync(output);
    }

    [FunctionName("ProcessServiceBus")]
    public async Task ProcessServiceBus(
        [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnection")] string message,
        [CosmosDB("mydb", "mycollection", Connection = "CosmosDBConnection")] IAsyncCollector<dynamic> documents,
        ILogger log)
    {
        // No connection strings - uses managed identity
        await documents.AddAsync(new { message, processedAt = DateTime.UtcNow });
    }
}

CI/CD Keyless Pipeline

# .github/workflows/keyless-deploy.yml
name: Keyless Deployment

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # No secrets needed for Azure login
      - name: Azure Login (OIDC)
        uses: azure/login@v1
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy
        run: |
          # All Azure commands use federated identity
          az deployment group create \
            --resource-group myResourceGroup \
            --template-file main.bicep

          # Access Key Vault without secrets
          az keyvault secret list --vault-name mykeyvault

          # Push to ACR without docker login
          az acr build \
            --registry myregistry \
            --image myapp:${{ github.sha }} \
            .

Best Practices for Keyless

from azure.identity import DefaultAzureCredential, ChainedTokenCredential, ManagedIdentityCredential, AzureCliCredential

class KeylessAuthenticator:
    """Best practices for keyless authentication."""

    def __init__(self, managed_identity_client_id: str = None):
        # Create credential chain
        credentials = []

        # Prefer managed identity in Azure
        if managed_identity_client_id:
            credentials.append(ManagedIdentityCredential(client_id=managed_identity_client_id))
        else:
            credentials.append(ManagedIdentityCredential())

        # Fall back to Azure CLI for local development
        credentials.append(AzureCliCredential())

        self.credential = ChainedTokenCredential(*credentials)

    def get_token(self, resource: str) -> str:
        """Get an access token for a specific resource."""
        token = self.credential.get_token(f"{resource}/.default")
        return token.token

    def test_connectivity(self):
        """Test that authentication is working."""
        resources = [
            "https://management.azure.com",
            "https://storage.azure.com",
            "https://vault.azure.net"
        ]

        for resource in resources:
            try:
                token = self.get_token(resource)
                print(f"Successfully authenticated to {resource}")
            except Exception as e:
                print(f"Failed to authenticate to {resource}: {e}")

Security Benefits

  1. No secret rotation - Tokens are short-lived
  2. No secret storage - Nothing to leak
  3. Audit trail - All access is logged
  4. Least privilege - Easy to implement
  5. Zero trust - Verify every request

Keyless authentication is the cornerstone of modern cloud security architecture.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.