or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdindex.mdoperations.mdpipeline.mdpolicies.mdserialization.mdservice-client.md
tile.json

authentication.mddocs/

Authentication Support

Built-in support for OAuth bearer token authentication with challenge handling for Azure services. Includes specialized handlers for Continuous Access Evaluation (CAE) and tenant-specific authentication challenges.

Capabilities

Claim Challenge Handler

Handles Continuous Access Evaluation (CAE) authentication challenges that require acquiring new tokens with additional claims.

/**
 * Handles Continuous Access Evaluation (CAE) authentication challenges
 * for bearer token authentication policies
 * @param onChallengeOptions - Configuration for handling the challenge
 * @returns Promise resolving to true if challenge was handled successfully
 */
async function authorizeRequestOnClaimChallenge(
  onChallengeOptions: AuthorizeRequestOnChallengeOptions
): Promise<boolean>;

Tenant Challenge Handler

Handles tenant-specific authentication challenges commonly used in Azure Storage APIs and other multi-tenant scenarios.

/**
 * Handles tenant-specific authentication challenges for bearer token policies
 * commonly used in Azure Storage APIs and multi-tenant scenarios
 * @param challengeOptions - Configuration for handling the tenant challenge
 * @returns Promise resolving to true if challenge was handled successfully
 */
const authorizeRequestOnTenantChallenge: (
  challengeOptions: AuthorizeRequestOnChallengeOptions
) => Promise<boolean>;

Challenge Function Parameters

The challenge handler functions use types from @azure/core-rest-pipeline and @azure/core-auth:

// These types are imported from @azure/core-rest-pipeline and @azure/core-auth
// They are not defined in @azure/core-client but are used by the challenge handlers

// From @azure/core-rest-pipeline:
interface AuthorizeRequestOnChallengeOptions {
  request: PipelineRequest;
  response: PipelineResponse;
  getAccessToken: (scopes: string | string[], options?: GetTokenOptions) => Promise<AccessToken | null>;
  scopes: string | string[];
}

// From @azure/core-auth:
interface GetTokenOptions {
  abortSignal?: AbortSignalLike;
  claims?: string;
  tenantId?: string;
}

interface AccessToken {
  token: string;
  expiresOnTimestamp: number;
}

Usage Examples

Setting Up Challenge Handlers with Pipeline

import { 
  createClientPipeline,
  authorizeRequestOnClaimChallenge,
  authorizeRequestOnTenantChallenge 
} from "@azure/core-client";
import { DefaultAzureCredential } from "@azure/identity";
import { bearerTokenAuthenticationPolicy } from "@azure/core-rest-pipeline";

const credential = new DefaultAzureCredential();
const scopes = ["https://storage.azure.com/.default"];

// Create pipeline with authentication
const pipeline = createClientPipeline({
  credentialOptions: {
    credential,
    credentialScopes: scopes
  }
});

// Add bearer token policy with challenge handlers
pipeline.addPolicy(bearerTokenAuthenticationPolicy({
  credential,
  scopes,
  challengeCallbacks: {
    // Handle CAE challenges
    authorizeRequestOnChallenge: authorizeRequestOnClaimChallenge,
    // Handle tenant-specific challenges
    authorizeRequestOnTenantChallenge: authorizeRequestOnTenantChallenge
  }
}));

Custom Challenge Handler Implementation

import type { 
  AuthorizeRequestOnChallengeOptions,
  PipelineRequest,
  PipelineResponse 
} from "@azure/core-client";

/**
 * Custom challenge handler that logs challenge details
 */
async function customChallengeHandler(
  options: AuthorizeRequestOnChallengeOptions
): Promise<boolean> {
  const { request, response, getAccessToken, scopes } = options;
  
  // Extract challenge information from response headers
  const wwwAuthenticate = response.headers.get("www-authenticate");
  if (!wwwAuthenticate) {
    return false;
  }
  
  // Parse challenge for additional claims or tenant information
  const claimsMatch = wwwAuthenticate.match(/claims="([^"]+)"/);
  const tenantMatch = wwwAuthenticate.match(/tenant_id="([^"]+)"/);
  
  try {
    // Request new token with additional requirements
    const tokenOptions: GetTokenOptions = {};
    
    if (claimsMatch) {
      tokenOptions.claims = claimsMatch[1];
      console.log("Handling CAE challenge with claims:", tokenOptions.claims);
    }
    
    if (tenantMatch) {
      tokenOptions.tenantId = tenantMatch[1];
      console.log("Handling tenant challenge for tenant:", tokenOptions.tenantId);
    }
    
    const accessToken = await getAccessToken(scopes, tokenOptions);
    if (!accessToken) {
      return false;
    }
    
    // Update request with new token
    request.headers.set("Authorization", `Bearer ${accessToken.token}`);
    return true;
    
  } catch (error) {
    console.error("Failed to handle authentication challenge:", error);
    return false;
  }
}

Integration with Service Client

import { 
  ServiceClient, 
  createClientPipeline,
  authorizeRequestOnClaimChallenge 
} from "@azure/core-client";
import { DefaultAzureCredential } from "@azure/identity";
import { bearerTokenAuthenticationPolicy } from "@azure/core-rest-pipeline";

class SecureAzureClient extends ServiceClient {
  constructor(endpoint: string) {
    const credential = new DefaultAzureCredential();
    const scopes = [`${endpoint}/.default`];
    
    // Create pipeline with authentication and challenge handling
    const pipeline = createClientPipeline({
      credentialOptions: {
        credential,
        credentialScopes: scopes
      }
    });
    
    // Configure bearer token policy with challenge callbacks
    pipeline.addPolicy(bearerTokenAuthenticationPolicy({
      credential,
      scopes,
      challengeCallbacks: {
        authorizeRequestOnChallenge: async (challengeOptions) => {
          console.log("Handling authentication challenge...");
          return authorizeRequestOnClaimChallenge(challengeOptions);
        }
      }
    }));
    
    super({ endpoint, pipeline });
  }
  
  async secureOperation(): Promise<any> {
    // This operation will automatically handle authentication challenges
    const operationSpec = {
      httpMethod: "GET" as const,
      path: "/secure-resource",
      serializer: createSerializer(),
      responses: {
        200: { bodyMapper: { type: { name: "Object" } } },
        401: { isError: true }, // Unauthorized - may trigger challenge
        403: { isError: true }  // Forbidden - may trigger challenge
      }
    };
    
    return this.sendOperationRequest({}, operationSpec);
  }
}

Multi-Tenant Authentication

import { 
  authorizeRequestOnTenantChallenge,
  createClientPipeline 
} from "@azure/core-client";
import { DefaultAzureCredential } from "@azure/identity";

// Multi-tenant client setup
class MultiTenantClient extends ServiceClient {
  constructor(endpoint: string, defaultTenantId?: string) {
    const credential = new DefaultAzureCredential({
      tenantId: defaultTenantId
    });
    
    const pipeline = createClientPipeline({
      credentialOptions: {
        credential,
        credentialScopes: [`${endpoint}/.default`]
      }
    });
    
    // Add tenant challenge handler
    pipeline.addPolicy(bearerTokenAuthenticationPolicy({
      credential,
      scopes: [`${endpoint}/.default`],
      challengeCallbacks: {
        authorizeRequestOnTenantChallenge: async (options) => {
          console.log("Handling tenant-specific challenge");
          return authorizeRequestOnTenantChallenge(options);
        }
      }
    }));
    
    super({ endpoint, pipeline });
  }
}

Storage Account Authentication

import { 
  ServiceClient,
  createClientPipeline,
  authorizeRequestOnTenantChallenge
} from "@azure/core-client";
import { DefaultAzureCredential } from "@azure/identity";

// Azure Storage client with tenant challenge handling
class StorageClient extends ServiceClient {
  constructor(storageAccountUrl: string) {
    const credential = new DefaultAzureCredential();
    
    const pipeline = createClientPipeline({
      credentialOptions: {
        credential,
        credentialScopes: ["https://storage.azure.com/.default"]
      }
    });
    
    // Storage services often use tenant challenges
    pipeline.addPolicy(bearerTokenAuthenticationPolicy({
      credential,
      scopes: ["https://storage.azure.com/.default"],
      challengeCallbacks: {
        // Handle tenant-specific authentication for storage
        authorizeRequestOnTenantChallenge: async (challengeOptions) => {
          console.log("Storage tenant challenge received");
          return authorizeRequestOnTenantChallenge(challengeOptions);
        }
      }
    }));
    
    super({ 
      endpoint: storageAccountUrl,
      pipeline 
    });
  }
}

Error Handling with Authentication

import { 
  ServiceClient,
  authorizeRequestOnClaimChallenge 
} from "@azure/core-client";

class ResilientClient extends ServiceClient {
  async performAuthenticatedRequest(operationSpec: OperationSpec): Promise<any> {
    try {
      return await this.sendOperationRequest({}, operationSpec);
    } catch (error) {
      // Check for authentication-related errors
      if (error.statusCode === 401) {
        console.log("Authentication failed - challenge may be required");
        
        // Check if challenge was handled
        const wwwAuth = error.response?.headers?.get("www-authenticate");
        if (wwwAuth?.includes("claims=")) {
          console.log("CAE challenge detected but not handled");
        } else if (wwwAuth?.includes("tenant_id=")) {
          console.log("Tenant challenge detected but not handled");
        }
      } else if (error.statusCode === 403) {
        console.log("Access forbidden - insufficient permissions");
      }
      
      throw error;
    }
  }
}

Challenge Flow Details

Continuous Access Evaluation (CAE) Flow

  1. Client makes request with current access token
  2. Service responds with 401 and www-authenticate header containing claims
  3. Challenge handler extracts claims from header
  4. New token is requested with additional claims
  5. Request is retried with new token

Tenant Challenge Flow

  1. Client makes request to multi-tenant service
  2. Service responds with 401 and tenant-specific challenge
  3. Challenge handler extracts tenant ID from response
  4. New token is requested for specific tenant
  5. Request is retried with tenant-specific token

Best Practices

Challenge Handler Configuration

// Recommended: Use both challenge handlers for comprehensive coverage
const challengeCallbacks = {
  authorizeRequestOnChallenge: authorizeRequestOnClaimChallenge,
  authorizeRequestOnTenantChallenge: authorizeRequestOnTenantChallenge
};

// Configure with retry policy for robust authentication
pipeline.addPolicy(retryPolicy({
  maxRetries: 3,
  retryDelayInMs: 1000
}));

pipeline.addPolicy(bearerTokenAuthenticationPolicy({
  credential,
  scopes,
  challengeCallbacks
}));

Token Caching

// Challenge handlers work with credential token caching
const credential = new DefaultAzureCredential({
  // Credentials automatically cache tokens
  // Challenge handlers will receive fresh tokens when needed
});

Error Logging

const customChallengeHandler = async (options: AuthorizeRequestOnChallengeOptions) => {
  try {
    const result = await authorizeRequestOnClaimChallenge(options);
    if (result) {
      console.log("Successfully handled authentication challenge");
    } else {
      console.warn("Challenge handler could not process request");
    }
    return result;
  } catch (error) {
    console.error("Challenge handler failed:", error);
    return false;
  }
};