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.
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>;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>;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;
}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
}
}));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;
}
}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);
}
}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 });
}
}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
});
}
}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;
}
}
}www-authenticate header containing claims// 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
}));// Challenge handlers work with credential token caching
const credential = new DefaultAzureCredential({
// Credentials automatically cache tokens
// Challenge handlers will receive fresh tokens when needed
});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;
}
};