msiTokenCredentials.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. import { Constants, WebResource, HttpClient, DefaultHttpClient } from "@azure/ms-rest-js";
  4. import { TokenClientCredentials, TokenResponse } from "./tokenClientCredentials";
  5. import { AuthConstants } from "../util/authConstants";
  6. /**
  7. * @interface MSIOptions Defines the optional parameters for authentication with MSI.
  8. */
  9. export interface MSIOptions {
  10. /**
  11. * @prop {string} [resource] - The resource uri or token audience for which the token is needed.
  12. * For e.g. it can be:
  13. * - resourcemanagement endpoint "https://management.azure.com/" (default)
  14. * - management endpoint "https://management.core.windows.net/"
  15. */
  16. resource?: string;
  17. /**
  18. * @property {HttpClient} [httpClient] - The client responsible for sending HTTP requests.
  19. * By default it is Axios-based {@link DefaultHttpClient}.
  20. */
  21. httpClient?: HttpClient;
  22. }
  23. /**
  24. * @interface MSITokenResponse - Describes the MSITokenResponse.
  25. */
  26. export interface MSITokenResponse extends TokenResponse {
  27. /**
  28. * @property {any} any - Placeholder for unknown properties.
  29. */
  30. readonly [x: string]: any;
  31. }
  32. /**
  33. * @class MSITokenCredentials - Provides information about managed service identity token credentials.
  34. * This object can only be used to acquire token on a virtual machine provisioned in Azure with managed service identity.
  35. */
  36. export abstract class MSITokenCredentials implements TokenClientCredentials {
  37. resource: string;
  38. protected _httpClient: HttpClient;
  39. /**
  40. * Creates an instance of MSITokenCredentials.
  41. * @param {object} [options] - Optional parameters
  42. * @param {string} [options.resource] - The resource uri or token audience for which the token is needed.
  43. * For e.g. it can be:
  44. * - resource management endpoint "https://management.azure.com/"(default)
  45. * - management endpoint "https://management.core.windows.net/"
  46. */
  47. constructor(options: MSIOptions) {
  48. if (!options) options = {};
  49. if (!options.resource) {
  50. options.resource = AuthConstants.RESOURCE_MANAGER_ENDPOINT;
  51. } else if (typeof options.resource.valueOf() !== "string") {
  52. throw new Error("resource must be a uri.");
  53. }
  54. this.resource = options.resource;
  55. this._httpClient = options.httpClient || new DefaultHttpClient();
  56. }
  57. /**
  58. * Parses a tokenResponse json string into a object, and converts properties on the first level to camelCase.
  59. * This method tries to standardize the tokenResponse
  60. * @param {string} body A json string
  61. * @return {object} [tokenResponse] The tokenResponse (tokenType and accessToken are the two important properties).
  62. */
  63. parseTokenResponse(body: string): TokenResponse {
  64. // Docs show different examples of possible MSI responses for different services. https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview
  65. // expires_on - is a Date like string in this doc
  66. // - https://docs.microsoft.com/en-us/azure/app-service/app-service-managed-service-identity#rest-protocol-examples
  67. // In other doc it is stringified number.
  68. // - https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/tutorial-linux-vm-access-arm#get-an-access-token-using-the-vms-identity-and-use-it-to-call-resource-manager
  69. const parsedBody = JSON.parse(body);
  70. parsedBody.accessToken = parsedBody["access_token"];
  71. delete parsedBody["access_token"];
  72. parsedBody.tokenType = parsedBody["token_type"];
  73. delete parsedBody["token_type"];
  74. if (parsedBody["refresh_token"]) {
  75. parsedBody.refreshToken = parsedBody["refresh_token"];
  76. delete parsedBody["refresh_token"];
  77. }
  78. if (parsedBody["expires_in"]) {
  79. parsedBody.expiresIn = parsedBody["expires_in"];
  80. if (typeof parsedBody["expires_in"] === "string") {
  81. // normal number as a string '1504130527'
  82. parsedBody.expiresIn = parseInt(parsedBody["expires_in"], 10);
  83. }
  84. delete parsedBody["expires_in"];
  85. }
  86. if (parsedBody["not_before"]) {
  87. parsedBody.notBefore = parsedBody["not_before"];
  88. if (typeof parsedBody["not_before"] === "string") {
  89. // normal number as a string '1504130527'
  90. parsedBody.notBefore = parseInt(parsedBody["not_before"], 10);
  91. }
  92. delete parsedBody["not_before"];
  93. }
  94. if (parsedBody["expires_on"]) {
  95. parsedBody.expiresOn = parsedBody["expires_on"];
  96. if (typeof parsedBody["expires_on"] === "string") {
  97. // possibly a Date string '09/14/2017 00:00:00 PM +00:00'
  98. if (parsedBody["expires_on"].includes(":") || parsedBody["expires_on"].includes("/")) {
  99. parsedBody.expiresOn = new Date(parsedBody["expires_on"], 10);
  100. } else {
  101. // normal number as a string '1504130527'
  102. parsedBody.expiresOn = new Date(parseInt(parsedBody["expires_on"], 10));
  103. }
  104. }
  105. delete parsedBody["expires_on"];
  106. }
  107. return parsedBody;
  108. }
  109. /**
  110. * Prepares and sends a POST request to a service endpoint hosted on the Azure VM, which responds with the access token.
  111. * @param {function} callback The callback in the form (err, result)
  112. * @return {Promise<MSITokenResponse>} Promise with the token response.
  113. */
  114. abstract async getToken(): Promise<MSITokenResponse>;
  115. protected abstract prepareRequestOptions(): WebResource;
  116. /**
  117. * Signs a request with the Authentication header.
  118. *
  119. * @param {webResource} The WebResource to be signed.
  120. * @return {Promise<WebResource>} Promise with signed WebResource.
  121. */
  122. public async signRequest(webResource: WebResource): Promise<WebResource> {
  123. const tokenResponse = await this.getToken();
  124. webResource.headers.set(Constants.HeaderConstants.AUTHORIZATION, `${tokenResponse.tokenType} ${tokenResponse.accessToken}`);
  125. return Promise.resolve(webResource);
  126. }
  127. }