wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
{"version":3,"file":"onBehalfOfCredential.js","sourceRoot":"","sources":["../../../src/credentials/onBehalfOfCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport type { MsalClient } from \"../msal/nodeFlows/msalClient.js\";\nimport { createMsalClient } from \"../msal/nodeFlows/msalClient.js\";\nimport type {\n  OnBehalfOfCredentialAssertionOptions,\n  OnBehalfOfCredentialCertificateOptions,\n  OnBehalfOfCredentialOptions,\n  OnBehalfOfCredentialSecretOptions,\n} from \"./onBehalfOfCredentialOptions.js\";\nimport { credentialLogger, formatError } from \"../util/logging.js\";\nimport {\n  processMultiTenantRequest,\n  resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils.js\";\n\nimport type { CertificateParts } from \"../msal/types.js\";\nimport type { ClientCertificatePEMCertificatePath } from \"./clientCertificateCredentialModels.js\";\nimport type { CredentialPersistenceOptions } from \"./credentialPersistenceOptions.js\";\nimport { CredentialUnavailableError } from \"../errors.js\";\nimport type { MultiTenantTokenCredentialOptions } from \"./multiTenantTokenCredentialOptions.js\";\nimport { createHash } from \"node:crypto\";\nimport { ensureScopes } from \"../util/scopeUtils.js\";\nimport { readFile } from \"node:fs/promises\";\nimport { tracingClient } from \"../util/tracing.js\";\n\nconst credentialName = \"OnBehalfOfCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * Enables authentication to Microsoft Entra ID using the [On Behalf Of flow](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow).\n */\nexport class OnBehalfOfCredential implements TokenCredential {\n  private tenantId: string;\n  private additionallyAllowedTenantIds: string[];\n  private msalClient: MsalClient;\n  private sendCertificateChain?: boolean;\n  private certificatePath?: string;\n  private clientSecret?: string;\n  private userAssertionToken: string;\n  private clientAssertion?: () => Promise<string>;\n\n  /**\n   * Creates an instance of the {@link OnBehalfOfCredential} with the details\n   * needed to authenticate against Microsoft Entra ID with path to a PEM certificate,\n   * and an user assertion.\n   *\n   * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n   *\n   * ```ts snippet:on_behalf_of_credential_pem_example\n   * import { OnBehalfOfCredential } from \"@azure/identity\";\n   * import { KeyClient } from \"@azure/keyvault-keys\";\n   *\n   * const tokenCredential = new OnBehalfOfCredential({\n   *   tenantId: \"tenant-id\",\n   *   clientId: \"client-id\",\n   *   certificatePath: \"/path/to/certificate.pem\",\n   *   userAssertionToken: \"access-token\",\n   * });\n   * const client = new KeyClient(\"vault-url\", tokenCredential);\n   *\n   * await client.getKey(\"key-name\");\n   * ```\n   *\n   * @param options - Optional parameters, generally common across credentials.\n   */\n  constructor(\n    options: OnBehalfOfCredentialCertificateOptions &\n      MultiTenantTokenCredentialOptions &\n      CredentialPersistenceOptions,\n  );\n  /**\n   * Creates an instance of the {@link OnBehalfOfCredential} with the details\n   * needed to authenticate against Microsoft Entra ID with a client\n   * secret and an user assertion.\n   *\n   * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n   *\n   * ```ts snippet:on_behalf_of_credential_secret_example\n   * import { OnBehalfOfCredential } from \"@azure/identity\";\n   * import { KeyClient } from \"@azure/keyvault-keys\";\n   *\n   * const tokenCredential = new OnBehalfOfCredential({\n   *   tenantId: \"tenant-id\",\n   *   clientId: \"client-id\",\n   *   clientSecret: \"client-secret\",\n   *   userAssertionToken: \"access-token\",\n   * });\n   * const client = new KeyClient(\"vault-url\", tokenCredential);\n   *\n   * await client.getKey(\"key-name\");\n   * ```\n   *\n   * @param options - Optional parameters, generally common across credentials.\n   */\n  constructor(\n    options: OnBehalfOfCredentialSecretOptions &\n      MultiTenantTokenCredentialOptions &\n      CredentialPersistenceOptions,\n  );\n\n  /**\n   * Creates an instance of the {@link OnBehalfOfCredential} with the details\n   * needed to authenticate against Microsoft Entra ID with a client `getAssertion`\n   * and an user assertion.\n   *\n   * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n   *\n   * ```ts snippet:on_behalf_of_credential_assertion_example\n   * import { OnBehalfOfCredential } from \"@azure/identity\";\n   * import { KeyClient } from \"@azure/keyvault-keys\";\n   *\n   * const tokenCredential = new OnBehalfOfCredential({\n   *   tenantId: \"tenant-id\",\n   *   clientId: \"client-id\",\n   *   getAssertion: () => {\n   *     return Promise.resolve(\"my-jwt\");\n   *   },\n   *   userAssertionToken: \"access-token\",\n   * });\n   * const client = new KeyClient(\"vault-url\", tokenCredential);\n   *\n   * await client.getKey(\"key-name\");\n   * ```\n   *\n   * @param options - Optional parameters, generally common across credentials.\n   */\n  constructor(\n    options: OnBehalfOfCredentialAssertionOptions &\n      MultiTenantTokenCredentialOptions &\n      CredentialPersistenceOptions,\n  );\n\n  constructor(options: OnBehalfOfCredentialOptions) {\n    const { clientSecret } = options as OnBehalfOfCredentialSecretOptions;\n    const { certificatePath, sendCertificateChain } =\n      options as OnBehalfOfCredentialCertificateOptions;\n    const { getAssertion } = options as OnBehalfOfCredentialAssertionOptions;\n    const {\n      tenantId,\n      clientId,\n      userAssertionToken,\n      additionallyAllowedTenants: additionallyAllowedTenantIds,\n    } = options;\n    if (!tenantId) {\n      throw new CredentialUnavailableError(\n        `${credentialName}: tenantId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n\n    if (!clientId) {\n      throw new CredentialUnavailableError(\n        `${credentialName}: clientId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n\n    if (!clientSecret && !certificatePath && !getAssertion) {\n      throw new CredentialUnavailableError(\n        `${credentialName}: You must provide one of clientSecret, certificatePath, or a getAssertion callback but none were provided. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n\n    if (!userAssertionToken) {\n      throw new CredentialUnavailableError(\n        `${credentialName}: userAssertionToken is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n    this.certificatePath = certificatePath;\n    this.clientSecret = clientSecret;\n    this.userAssertionToken = userAssertionToken;\n    this.sendCertificateChain = sendCertificateChain;\n    this.clientAssertion = getAssertion;\n\n    this.tenantId = tenantId;\n    this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n      additionallyAllowedTenantIds,\n    );\n\n    this.msalClient = createMsalClient(clientId, this.tenantId, {\n      ...options,\n      logger,\n      tokenCredentialOptions: options,\n    });\n  }\n\n  /**\n   * Authenticates with Microsoft Entra ID and returns an access token if successful.\n   * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n   *\n   * @param scopes - The list of scopes for which the token will have access.\n   * @param options - The options used to configure the underlying network requests.\n   */\n  async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n    return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n      newOptions.tenantId = processMultiTenantRequest(\n        this.tenantId,\n        newOptions,\n        this.additionallyAllowedTenantIds,\n        logger,\n      );\n\n      const arrayScopes = ensureScopes(scopes);\n      if (this.certificatePath) {\n        const clientCertificate = await this.buildClientCertificate(this.certificatePath);\n\n        return this.msalClient.getTokenOnBehalfOf(\n          arrayScopes,\n          this.userAssertionToken,\n          clientCertificate,\n          newOptions,\n        );\n      } else if (this.clientSecret) {\n        return this.msalClient.getTokenOnBehalfOf(\n          arrayScopes,\n          this.userAssertionToken,\n          this.clientSecret,\n          options,\n        );\n      } else if (this.clientAssertion) {\n        return this.msalClient.getTokenOnBehalfOf(\n          arrayScopes,\n          this.userAssertionToken,\n          this.clientAssertion,\n          options,\n        );\n      } else {\n        // this is an invalid scenario and is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath nor clientAssertion were provided\n        throw new Error(\n          \"Expected either clientSecret or certificatePath or clientAssertion to be defined.\",\n        );\n      }\n    });\n  }\n\n  private async buildClientCertificate(certificatePath: string): Promise<CertificateParts> {\n    try {\n      const parts = await this.parseCertificate({ certificatePath }, this.sendCertificateChain);\n      return {\n        thumbprint: parts.thumbprint,\n        thumbprintSha256: parts.thumbprintSha256,\n        privateKey: parts.certificateContents,\n        x5c: parts.x5c,\n      };\n    } catch (error: any) {\n      logger.info(formatError(\"\", error));\n      throw error;\n    }\n  }\n\n  private async parseCertificate(\n    configuration: ClientCertificatePEMCertificatePath,\n    sendCertificateChain?: boolean,\n  ): Promise<Omit<CertificateParts, \"privateKey\"> & { certificateContents: string }> {\n    const certificatePath = configuration.certificatePath;\n    const certificateContents = await readFile(certificatePath, \"utf8\");\n    const x5c = sendCertificateChain ? certificateContents : undefined;\n\n    const certificatePattern =\n      /(-+BEGIN CERTIFICATE-+)(\\n\\r?|\\r\\n?)([A-Za-z0-9+/\\n\\r]+=*)(\\n\\r?|\\r\\n?)(-+END CERTIFICATE-+)/g;\n    const publicKeys: string[] = [];\n\n    // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c\n    let match;\n    do {\n      match = certificatePattern.exec(certificateContents);\n      if (match) {\n        publicKeys.push(match[3]);\n      }\n    } while (match);\n\n    if (publicKeys.length === 0) {\n      throw new Error(\"The file at the specified path does not contain a PEM-encoded certificate.\");\n    }\n    const thumbprint = createHash(\"sha1\") // CodeQL [SM04514] Needed for backward compatibility reason\n      .update(Buffer.from(publicKeys[0], \"base64\"))\n      .digest(\"hex\")\n      .toUpperCase();\n\n    const thumbprintSha256 = createHash(\"sha256\")\n      .update(Buffer.from(publicKeys[0], \"base64\"))\n      .digest(\"hex\")\n      .toUpperCase();\n\n    return {\n      certificateContents,\n      thumbprintSha256,\n      thumbprint,\n      x5c,\n    };\n  }\n}\n"]}