wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
{
  "version": 3,
  "sources": ["../../src/keyVaultAuthenticationPolicy.ts"],
  "sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n  PipelinePolicy,\n  PipelineRequest,\n  PipelineResponse,\n  RequestBodyType,\n  SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n  | {\n      status: \"none\";\n    }\n  | {\n      status: \"started\";\n      originalBody?: RequestBodyType;\n    }\n  | {\n      status: \"complete\";\n      scopes: string[];\n      tenantId?: string;\n    };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n  /**\n   * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n   *\n   * Defaults to false.\n   */\n  disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n  let scopeAsUrl: URL;\n  try {\n    scopeAsUrl = new URL(scope);\n  } catch (e) {\n    throw new Error(`The challenge contains invalid scope '${scope}'`);\n  }\n\n  const requestUrl = new URL(request.url);\n\n  if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n    throw new Error(\n      `The challenge resource '${scopeAsUrl.hostname}' does not match the requested domain. Set disableChallengeResourceVerification to true in your client options to disable. See https://aka.ms/azsdk/blog/vault-uri for more information.`,\n    );\n  }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n  credential: TokenCredential,\n  options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n  const { disableChallengeResourceVerification } = options;\n  let challengeState: ChallengeState = { status: \"none\" };\n  const getAccessToken = createTokenCycler(credential);\n\n  function requestToOptions(request: PipelineRequest): GetTokenOptions {\n    return {\n      abortSignal: request.abortSignal,\n      requestOptions: {\n        timeout: request.timeout > 0 ? request.timeout : undefined,\n      },\n      tracingOptions: request.tracingOptions,\n    };\n  }\n\n  async function authorizeRequest(request: PipelineRequest): Promise<void> {\n    const requestOptions: GetTokenOptions = requestToOptions(request);\n\n    switch (challengeState.status) {\n      case \"none\":\n        challengeState = {\n          status: \"started\",\n          originalBody: request.body,\n        };\n        request.body = null;\n        break;\n      case \"started\":\n        break; // Retry, we should not overwrite the original body\n      case \"complete\": {\n        const token = await getAccessToken(challengeState.scopes, {\n          ...requestOptions,\n          enableCae: true,\n          tenantId: challengeState.tenantId,\n        });\n        if (token) {\n          request.headers.set(\"authorization\", `Bearer ${token.token}`);\n        }\n        break;\n      }\n    }\n  }\n\n  async function handleChallenge(\n    request: PipelineRequest,\n    response: PipelineResponse,\n    next: SendRequest,\n  ): Promise<PipelineResponse> {\n    // If status is not 401, this is a no-op\n    if (response.status !== 401) {\n      return response;\n    }\n\n    if (request.body === null && challengeState.status === \"started\") {\n      // Reset the original body before doing anything else.\n      // Note: If successful status will be \"complete\", otherwise \"none\" will\n      // restart the process.\n      request.body = challengeState.originalBody;\n    }\n\n    const getTokenOptions = requestToOptions(request);\n\n    const challenge = response.headers.get(\"WWW-Authenticate\");\n    if (!challenge) {\n      logger.warning(\n        \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n      );\n      return response;\n    }\n    const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n    const scope = parsedChallenge.resource\n      ? parsedChallenge.resource + \"/.default\"\n      : parsedChallenge.scope;\n\n    if (!scope) {\n      // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n      return response;\n    }\n\n    if (!disableChallengeResourceVerification) {\n      verifyChallengeResource(scope, request);\n    }\n\n    const accessToken = await getAccessToken([scope], {\n      ...getTokenOptions,\n      enableCae: true,\n      tenantId: parsedChallenge.tenantId,\n    });\n\n    if (!accessToken) {\n      // No access token provided, treat as no-op\n      return response;\n    }\n\n    request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n    challengeState = {\n      status: \"complete\",\n      scopes: [scope],\n      tenantId: parsedChallenge.tenantId,\n    };\n\n    // We have a token now, so try send the request again\n    return next(request);\n  }\n\n  async function handleCaeChallenge(\n    request: PipelineRequest,\n    response: PipelineResponse,\n    next: SendRequest,\n  ): Promise<PipelineResponse> {\n    // Cannot handle CAE challenge if a regular challenge has not been completed first\n    if (challengeState.status !== \"complete\") {\n      return response;\n    }\n\n    // If status is not 401, this is a no-op\n    if (response.status !== 401) {\n      return response;\n    }\n\n    const getTokenOptions = requestToOptions(request);\n\n    const challenge = response.headers.get(\"WWW-Authenticate\");\n    if (!challenge) {\n      return response;\n    }\n    const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n      parseWWWAuthenticateHeader(challenge);\n\n    if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n      return response;\n    }\n\n    const claims = atob(base64EncodedClaims);\n\n    const accessToken = await getAccessToken(challengeState.scopes, {\n      ...getTokenOptions,\n      enableCae: true,\n      tenantId: challengeState.tenantId,\n      claims,\n    });\n\n    request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n    return next(request);\n  }\n\n  async function sendRequest(\n    request: PipelineRequest,\n    next: SendRequest,\n  ): Promise<PipelineResponse> {\n    // Add token if possible\n    await authorizeRequest(request);\n\n    // Try send request (first attempt)\n    let response = await next(request);\n\n    // Handle standard challenge if present\n    response = await handleChallenge(request, response, next);\n\n    // Handle CAE challenge if present\n    response = await handleCaeChallenge(request, response, next);\n\n    return response;\n  }\n\n  return {\n    name: keyVaultAuthenticationPolicyName,\n    sendRequest,\n  };\n}\n"],
  "mappings": "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",
  "names": []
}