Socket
Socket
Sign inDemoInstall

@azure/identity

Package Overview
Dependencies
Maintainers
2
Versions
518
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@azure/identity - npm Package Compare versions

Comparing version 1.0.0-preview.1 to 1.0.0-preview.2

browser/identity.js

2

dist-esm/src/client/errors.d.ts

@@ -25,3 +25,3 @@ /**

readonly errorResponse: ErrorResponse;
constructor(statusCode: number, errorBody: string | undefined | null);
constructor(statusCode: number, errorBody: object | string | undefined | null);
}

@@ -28,0 +28,0 @@ /**

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
function isErrorResponse(errorResponse) {
return errorResponse &&
typeof errorResponse.error === "string" &&
typeof errorResponse.error_description === "string";
}
/**

@@ -14,3 +19,6 @@ * Provides details about a failure to authenticate with Azure Active

};
if (errorBody) {
if (isErrorResponse(errorBody)) {
errorResponse = errorBody;
}
else if (typeof errorBody === "string") {
try {

@@ -17,0 +25,0 @@ // Most error responses will contain JSON-formatted error details

@@ -1,24 +0,27 @@

import { AccessToken, ServiceClient, ServiceClientOptions, GetTokenOptions } from "@azure/core-http";
export declare const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export declare const ImdsApiVersion = "2018-02-01";
export declare const AppServiceMsiApiVersion = "2017-09-01";
import { AccessToken, ServiceClient, ServiceClientOptions, WebResource, RequestPrepareOptions, GetTokenOptions } from "@azure/core-http";
/**
* An internal type used to communicate details of a token request's
* response that should not be sent back as part of the AccessToken.
*/
export interface TokenResponse {
/**
* The AccessToken to be returned from getToken.
*/
accessToken: AccessToken;
/**
* The refresh token if the 'offline_access' scope was used.
*/
refreshToken?: string;
}
export declare class IdentityClient extends ServiceClient {
authorityHost: string;
constructor(options?: IdentityClientOptions);
private createWebResource;
private sendTokenRequest;
private mapScopesToResource;
private dateInSeconds;
private addMinutes;
private createImdsAuthRequest;
private createAppServiceMsiAuthRequest;
private createCloudShellMsiAuthRequest;
private pingImdsEndpoint;
authenticateClientSecret(tenantId: string, clientId: string, clientSecret: string, scopes: string | string[], getTokenOptions?: GetTokenOptions): Promise<AccessToken | null>;
authenticateManagedIdentity(scopes: string | string[], checkIfImdsEndpointAvailable: boolean, clientId?: string, getTokenOptions?: GetTokenOptions): Promise<AccessToken | null>;
authenticateClientCertificate(tenantId: string, clientId: string, certificateString: string, certificateX5t: string, scopes: string | string[], getTokenOptions?: GetTokenOptions): Promise<AccessToken | null>;
createWebResource(requestOptions: RequestPrepareOptions): WebResource;
sendTokenRequest(webResource: WebResource, expiresOnParser?: (responseBody: any) => number): Promise<TokenResponse | null>;
refreshAccessToken(tenantId: string, clientId: string, scopes: string, refreshToken: string | undefined, clientSecret: string | undefined, expiresOnParser?: (responseBody: any) => number, options?: GetTokenOptions): Promise<TokenResponse | null>;
static getDefaultOptions(): IdentityClientOptions;
}
export interface IdentityClientOptions extends ServiceClientOptions {
authorityHost: string;
authorityHost?: string;
}
//# sourceMappingURL=identityClient.d.ts.map

@@ -5,12 +5,5 @@ // Copyright (c) Microsoft Corporation.

import qs from "qs";
import jws from "jws";
import uuid from "uuid";
import { ServiceClient, WebResource, RestError } from "@azure/core-http";
import { ServiceClient, WebResource } from "@azure/core-http";
import { AuthenticationError } from "./errors";
const SelfSignedJwtLifetimeMins = 10;
const DefaultAuthorityHost = "https://login.microsoftonline.com";
const DefaultScopeSuffix = "/.default";
export const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export const ImdsApiVersion = "2018-02-01";
export const AppServiceMsiApiVersion = "2017-09-01";
export class IdentityClient extends ServiceClient {

@@ -20,3 +13,6 @@ constructor(options) {

super(undefined, options);
this.baseUri = options.authorityHost;
this.baseUri = this.authorityHost = options.authorityHost || DefaultAuthorityHost;
if (!this.baseUri.startsWith("https:")) {
throw new Error("The authorityHost address must use the 'https' protocol.");
}
}

@@ -36,216 +32,56 @@ createWebResource(requestOptions) {

return {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
accessToken: {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
},
refreshToken: response.parsedBody.refresh_token,
};
}
else {
throw new AuthenticationError(response.status, response.bodyAsText);
throw new AuthenticationError(response.status, response.parsedBody || response.bodyAsText);
}
});
}
mapScopesToResource(scopes) {
let scope = "";
if (Array.isArray(scopes)) {
if (scopes.length !== 1) {
throw "To convert to a resource string the specified array must be exactly length 1";
refreshAccessToken(tenantId, clientId, scopes, refreshToken, clientSecret, expiresOnParser, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (refreshToken === undefined) {
return null;
}
scope = scopes[0];
}
else if (typeof scopes === "string") {
scope = scopes;
}
if (!scope.endsWith(DefaultScopeSuffix)) {
return scope;
}
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
}
dateInSeconds(date) {
return Math.floor(date.getTime() / 1000);
}
addMinutes(date, minutes) {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
createImdsAuthRequest(resource, clientId) {
const queryParameters = {
resource,
"api-version": ImdsApiVersion
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: ImdsEndpoint,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
Metadata: true
const refreshParams = {
grant_type: "refresh_token",
client_id: clientId,
refresh_token: refreshToken,
scope: scopes
};
if (clientSecret !== undefined) {
refreshParams.client_secret = clientSecret;
}
};
}
createAppServiceMsiAuthRequest(resource, clientId) {
const queryParameters = {
resource,
"api-version": AppServiceMsiApiVersion,
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
secret: process.env.MSI_SECRET
}
};
}
createCloudShellMsiAuthRequest(resource, clientId) {
const body = {
resource
};
if (clientId) {
body.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "POST",
body: qs.stringify(body),
headers: {
Accept: "application/json",
Metadata: true,
"Content-Type": "application/x-www-form-urlencoded"
}
};
}
pingImdsEndpoint(resource, clientId) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const request = this.createImdsAuthRequest(resource, clientId);
// This will always be populated, but let's make TypeScript happy
if (request.headers) {
// Remove the Metadata header to invoke a request error from
// IMDS endpoint
delete request.headers.Metadata;
}
// Create a request with a 500 msec timeout since we expect that
// not having a "Metadata" header should cause an error to be
// returned quickly from the endpoint, proving its availability.
const webResource = this.createWebResource(request);
webResource.timeout = 500;
const webResource = this.createWebResource({
url: `${this.authorityHost}/${tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify(refreshParams),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
try {
yield this.sendRequest(webResource);
return yield this.sendTokenRequest(webResource, expiresOnParser);
}
catch (err) {
if (err instanceof RestError && err.code === RestError.REQUEST_SEND_ERROR) {
// Either request failed or IMDS endpoint isn't available
return false;
if (err instanceof AuthenticationError && err.errorResponse.error === "interaction_required") {
// It's likely that the refresh token has expired, so
// return null so that the credential implementation will
// initiate the authentication flow again.
return null;
}
}
// If we received any response, the endpoint is available
return true;
});
}
authenticateClientSecret(tenantId, clientId, clientSecret, scopes, getTokenOptions) {
const webResource = this.createWebResource({
url: `${this.baseUri}/${tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
});
return this.sendTokenRequest(webResource);
}
authenticateManagedIdentity(scopes, checkIfImdsEndpointAvailable, clientId, getTokenOptions) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
let authRequestOptions;
const resource = this.mapScopesToResource(scopes);
let expiresInParser;
// Detect which type of environment we are running in
if (process.env.MSI_ENDPOINT) {
if (process.env.MSI_SECRET) {
// Running in App Service
authRequestOptions = this.createAppServiceMsiAuthRequest(resource, clientId);
expiresInParser = (requestBody) => {
// Parse a date format like "06/20/2019 02:57:58 +00:00" and
// convert it into a JavaScript-formatted date
const m = requestBody.expires_on.match(/(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d) (\+|-)(\d\d):(\d\d)/);
return Date.parse(`${m[3]}-${m[1]}-${m[2]}T${m[4]}:${m[5]}:${m[6]}${m[7]}${m[8]}:${m[9]}`);
};
}
else {
// Running in Cloud Shell
authRequestOptions = this.createCloudShellMsiAuthRequest(resource, clientId);
throw err;
}
}
else {
// Ping the IMDS endpoint to see if it's available
if (!checkIfImdsEndpointAvailable || (yield this.pingImdsEndpoint(resource, clientId))) {
// Running in an Azure VM
authRequestOptions = this.createImdsAuthRequest(resource, clientId);
}
else {
// Returning null tells the ManagedIdentityCredential that
// no MSI authentication endpoints are available
return null;
}
}
const webResource = this.createWebResource(Object.assign({ disableJsonStringifyOnBody: true, deserializationMapper: undefined, abortSignal: getTokenOptions && getTokenOptions.abortSignal }, authRequestOptions));
return this.sendTokenRequest(webResource, expiresInParser);
});
}
authenticateClientCertificate(tenantId, clientId, certificateString, certificateX5t, scopes, getTokenOptions) {
const tokenId = uuid.v4();
const audienceUrl = `${this.baseUri}/${tenantId}/oauth2/v2.0/token`;
const header = {
typ: "JWT",
alg: "RS256",
x5t: certificateX5t
};
const payload = {
iss: clientId,
sub: clientId,
aud: audienceUrl,
jti: tokenId,
nbf: this.dateInSeconds(new Date()),
exp: this.dateInSeconds(this.addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: certificateString
});
const webResource = this.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
});
return this.sendTokenRequest(webResource);
}
static getDefaultOptions() {

@@ -252,0 +88,0 @@ return {

@@ -13,7 +13,7 @@ import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";

private identityClient;
private _tenantId;
private _clientId;
private _certificateString;
certificateThumbprint: string;
certificateX5t: string;
private tenantId;
private clientId;
private certificateString;
private certificateThumbprint;
private certificateX5t;
/**

@@ -20,0 +20,0 @@ * Creates an instance of the ClientCertificateCredential with the details

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as tslib_1 from "tslib";
import qs from "qs";
import jws from "jws";
import uuid from "uuid";
import { readFileSync } from "fs";
import { createHash } from "crypto";
import { IdentityClient } from "../client/identityClient";
const SelfSignedJwtLifetimeMins = 10;
function timestampInSeconds(date) {
return Math.floor(date.getTime() / 1000);
}
function addMinutes(date, minutes) {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
/**

@@ -26,7 +38,7 @@ * Enables authentication to Azure Active Directory using a PEM-encoded

this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this._certificateString = readFileSync(certificatePath, "utf8");
this.tenantId = tenantId;
this.clientId = clientId;
this.certificateString = readFileSync(certificatePath, "utf8");
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
const matchCert = this._certificateString.match(certificatePattern);
const matchCert = this.certificateString.match(certificatePattern);
const publicKey = matchCert ? matchCert[3] : "";

@@ -53,5 +65,47 @@ if (!publicKey) {

getToken(scopes, options) {
return this.identityClient.authenticateClientCertificate(this._tenantId, this._clientId, this._certificateString, this.certificateX5t, scopes, options);
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const tokenId = uuid.v4();
const audienceUrl = `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`;
const header = {
typ: "JWT",
alg: "RS256",
x5t: this.certificateX5t
};
const payload = {
iss: this.clientId,
sub: this.clientId,
aud: audienceUrl,
jti: tokenId,
nbf: timestampInSeconds(new Date()),
exp: timestampInSeconds(addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: this.certificateString
});
const webResource = this.identityClient.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
}
//# sourceMappingURL=clientCertificateCredential.js.map

@@ -13,5 +13,5 @@ import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";

private identityClient;
private _tenantId;
private _clientId;
private _clientSecret;
private tenantId;
private clientId;
private clientSecret;
/**

@@ -18,0 +18,0 @@ * Creates an instance of the ClientSecretCredential with the details

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as tslib_1 from "tslib";
import qs from "qs";
import { IdentityClient } from "../client/identityClient";

@@ -25,5 +27,5 @@ /**

this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this._clientSecret = clientSecret;
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}

@@ -41,5 +43,26 @@ /**

getToken(scopes, options) {
return this.identityClient.authenticateClientSecret(this._tenantId, this._clientId, this._clientSecret, scopes, options);
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
}
//# sourceMappingURL=clientSecretCredential.js.map
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { isNode } from "@azure/core-http";
import { ClientSecretCredential } from "./clientSecretCredential";

@@ -28,5 +27,2 @@ /**

this._credential = undefined;
if (!isNode) {
throw "EnvironmentCredential is only supported when running in Node.js.";
}
const tenantId = process.env.AZURE_TENANT_ID, clientId = process.env.AZURE_CLIENT_ID, clientSecret = process.env.AZURE_CLIENT_SECRET;

@@ -33,0 +29,0 @@ if (tenantId && clientId && clientSecret) {

import { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-http";
import { IdentityClientOptions } from "../client/identityClient";
export declare const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export declare const ImdsApiVersion = "2018-02-01";
export declare const AppServiceMsiApiVersion = "2017-09-01";
/**

@@ -14,5 +17,11 @@ * Attempts authentication using a managed identity that has been assigned

private identityClient;
private _clientId;
private clientId;
private isEndpointUnavailable;
constructor(clientId?: string, options?: IdentityClientOptions);
private mapScopesToResource;
private createImdsAuthRequest;
private createAppServiceMsiAuthRequest;
private createCloudShellMsiAuthRequest;
private pingImdsEndpoint;
private authenticateManagedIdentity;
/**

@@ -19,0 +28,0 @@ * Authenticates with Azure Active Directory and returns an {@link AccessToken} if

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as tslib_1 from "tslib";
import qs from "qs";
import { RestError } from "@azure/core-http";
import { IdentityClient } from "../client/identityClient";
const DefaultScopeSuffix = "/.default";
export const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export const ImdsApiVersion = "2018-02-01";
export const AppServiceMsiApiVersion = "2017-09-01";
/**

@@ -18,4 +24,140 @@ * Attempts authentication using a managed identity that has been assigned

this.identityClient = new IdentityClient(options);
this._clientId = clientId;
this.clientId = clientId;
}
mapScopesToResource(scopes) {
let scope = "";
if (Array.isArray(scopes)) {
if (scopes.length !== 1) {
throw "To convert to a resource string the specified array must be exactly length 1";
}
scope = scopes[0];
}
else if (typeof scopes === "string") {
scope = scopes;
}
if (!scope.endsWith(DefaultScopeSuffix)) {
return scope;
}
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
}
createImdsAuthRequest(resource, clientId) {
const queryParameters = {
resource,
"api-version": ImdsApiVersion
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: ImdsEndpoint,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
Metadata: true
}
};
}
createAppServiceMsiAuthRequest(resource, clientId) {
const queryParameters = {
resource,
"api-version": AppServiceMsiApiVersion,
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
secret: process.env.MSI_SECRET
}
};
}
createCloudShellMsiAuthRequest(resource, clientId) {
const body = {
resource
};
if (clientId) {
body.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "POST",
body: qs.stringify(body),
headers: {
Accept: "application/json",
Metadata: true,
"Content-Type": "application/x-www-form-urlencoded"
}
};
}
pingImdsEndpoint(resource, clientId) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const request = this.createImdsAuthRequest(resource, clientId);
// This will always be populated, but let's make TypeScript happy
if (request.headers) {
// Remove the Metadata header to invoke a request error from
// IMDS endpoint
delete request.headers.Metadata;
}
// Create a request with a 500 msec timeout since we expect that
// not having a "Metadata" header should cause an error to be
// returned quickly from the endpoint, proving its availability.
const webResource = this.identityClient.createWebResource(request);
webResource.timeout = 500;
try {
yield this.identityClient.sendRequest(webResource);
}
catch (err) {
if (err instanceof RestError && err.code === RestError.REQUEST_SEND_ERROR) {
// Either request failed or IMDS endpoint isn't available
return false;
}
}
// If we received any response, the endpoint is available
return true;
});
}
authenticateManagedIdentity(scopes, checkIfImdsEndpointAvailable, clientId, getTokenOptions) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
let authRequestOptions;
const resource = this.mapScopesToResource(scopes);
let expiresInParser;
// Detect which type of environment we are running in
if (process.env.MSI_ENDPOINT) {
if (process.env.MSI_SECRET) {
// Running in App Service
authRequestOptions = this.createAppServiceMsiAuthRequest(resource, clientId);
expiresInParser = (requestBody) => {
// Parse a date format like "06/20/2019 02:57:58 +00:00" and
// convert it into a JavaScript-formatted date
const m = requestBody.expires_on.match(/(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d) (\+|-)(\d\d):(\d\d)/);
return Date.parse(`${m[3]}-${m[1]}-${m[2]}T${m[4]}:${m[5]}:${m[6]}${m[7]}${m[8]}:${m[9]}`);
};
}
else {
// Running in Cloud Shell
authRequestOptions = this.createCloudShellMsiAuthRequest(resource, clientId);
}
}
else {
// Ping the IMDS endpoint to see if it's available
if (!checkIfImdsEndpointAvailable || (yield this.pingImdsEndpoint(resource, clientId))) {
// Running in an Azure VM
authRequestOptions = this.createImdsAuthRequest(resource, clientId);
}
else {
// Returning null tells the ManagedIdentityCredential that
// no MSI authentication endpoints are available
return null;
}
}
const webResource = this.identityClient.createWebResource(Object.assign({ disableJsonStringifyOnBody: true, deserializationMapper: undefined, abortSignal: getTokenOptions && getTokenOptions.abortSignal }, authRequestOptions));
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource, expiresInParser);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
/**

@@ -39,3 +181,3 @@ * Authenticates with Azure Active Directory and returns an {@link AccessToken} if

result =
yield this.identityClient.authenticateManagedIdentity(scopes, this.isEndpointUnavailable === null, this._clientId, options);
yield this.authenticateManagedIdentity(scopes, this.isEndpointUnavailable === null, this.clientId, options);
// If authenticateManagedIdentity returns null, it means no MSI

@@ -42,0 +184,0 @@ // endpoints are available. In this case, don't try them in future

@@ -7,4 +7,8 @@ import { TokenCredential } from "@azure/core-http";

export { ClientCertificateCredential } from "./credentials/clientCertificateCredential";
export { InteractiveBrowserCredential } from "./credentials/interactiveBrowserCredential";
export { InteractiveBrowserCredentialOptions, BrowserLoginStyle } from "./credentials/interactiveBrowserCredentialOptions";
export { ManagedIdentityCredential } from "./credentials/managedIdentityCredential";
export { DeviceCodeCredential } from "./credentials/deviceCodeCredential";
export { DefaultAzureCredential } from "./credentials/defaultAzureCredential";
export { UsernamePasswordCredential } from "./credentials/usernamePasswordCredential";
export { AuthenticationError, AggregateAuthenticationError } from "./client/errors";

@@ -11,0 +15,0 @@ export { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";

@@ -8,4 +8,7 @@ // Copyright (c) Microsoft Corporation.

export { ClientCertificateCredential } from "./credentials/clientCertificateCredential";
export { InteractiveBrowserCredential } from "./credentials/interactiveBrowserCredential";
export { ManagedIdentityCredential } from "./credentials/managedIdentityCredential";
export { DeviceCodeCredential } from "./credentials/deviceCodeCredential";
export { DefaultAzureCredential } from "./credentials/defaultAzureCredential";
export { UsernamePasswordCredential } from "./credentials/usernamePasswordCredential";
export { AuthenticationError, AggregateAuthenticationError } from "./client/errors";

@@ -12,0 +15,0 @@ export function getDefaultAzureCredential() {

@@ -8,4 +8,4 @@ 'use strict';

var tslib_1 = require('tslib');
var qs = _interopDefault(require('qs'));
var coreHttp = require('@azure/core-http');
var qs = _interopDefault(require('qs'));
var jws = _interopDefault(require('jws'));

@@ -18,2 +18,7 @@ var uuid = _interopDefault(require('uuid'));

// Licensed under the MIT License.
function isErrorResponse(errorResponse) {
return errorResponse &&
typeof errorResponse.error === "string" &&
typeof errorResponse.error_description === "string";
}
/**

@@ -30,3 +35,6 @@ * Provides details about a failure to authenticate with Azure Active

};
if (errorBody) {
if (isErrorResponse(errorBody)) {
errorResponse = errorBody;
}
else if (typeof errorBody === "string") {
try {

@@ -119,8 +127,3 @@ // Most error responses will contain JSON-formatted error details

// Copyright (c) Microsoft Corporation.
const SelfSignedJwtLifetimeMins = 10;
const DefaultAuthorityHost = "https://login.microsoftonline.com";
const DefaultScopeSuffix = "/.default";
const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
const ImdsApiVersion = "2018-02-01";
const AppServiceMsiApiVersion = "2017-09-01";
class IdentityClient extends coreHttp.ServiceClient {

@@ -130,3 +133,6 @@ constructor(options) {

super(undefined, options);
this.baseUri = options.authorityHost;
this.baseUri = this.authorityHost = options.authorityHost || DefaultAuthorityHost;
if (!this.baseUri.startsWith("https:")) {
throw new Error("The authorityHost address must use the 'https' protocol.");
}
}

@@ -146,11 +152,192 @@ createWebResource(requestOptions) {

return {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
accessToken: {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
},
refreshToken: response.parsedBody.refresh_token,
};
}
else {
throw new AuthenticationError(response.status, response.bodyAsText);
throw new AuthenticationError(response.status, response.parsedBody || response.bodyAsText);
}
});
}
refreshAccessToken(tenantId, clientId, scopes, refreshToken, clientSecret, expiresOnParser, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (refreshToken === undefined) {
return null;
}
const refreshParams = {
grant_type: "refresh_token",
client_id: clientId,
refresh_token: refreshToken,
scope: scopes
};
if (clientSecret !== undefined) {
refreshParams.client_secret = clientSecret;
}
const webResource = this.createWebResource({
url: `${this.authorityHost}/${tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify(refreshParams),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
try {
return yield this.sendTokenRequest(webResource, expiresOnParser);
}
catch (err) {
if (err instanceof AuthenticationError && err.errorResponse.error === "interaction_required") {
// It's likely that the refresh token has expired, so
// return null so that the credential implementation will
// initiate the authentication flow again.
return null;
}
else {
throw err;
}
}
});
}
static getDefaultOptions() {
return {
authorityHost: DefaultAuthorityHost
};
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory using a client secret
* that was generated for an App Registration. More information on how
* to configure a client secret can be found here:
*
* https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application
*
*/
class ClientSecretCredential {
/**
* Creates an instance of the ClientSecretCredential with the details
* needed to authenticate against Azure Active Directory with a client
* secret.
*
* @param tenantId The Azure Active Directory tenant (directory) ID.
* @param clientId The client (application) ID of an App Registration in the tenant.
* @param clientSecret A client secret that was generated for the App Registration.
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, clientSecret, options) {
this.identityClient = new IdentityClient(options);
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory using client secret
* details configured in the following environment variables:
*
* - AZURE_TENANT_ID: The Azure Active Directory tenant (directory) ID.
* - AZURE_CLIENT_ID: The client (application) ID of an App Registration in the tenant.
* - AZURE_CLIENT_SECRET: A client secret that was generated for the App Registration.
*
* This credential ultimately uses a {@link ClientSecretCredential} to
* perform the authentication using these details. Please consult the
* documentation of that class for more details.
*/
class EnvironmentCredential {
/**
* Creates an instance of the EnvironmentCredential class and reads
* client secret details from environment variables. If the expected
* environment variables are not found at this time, the getToken method
* will return null when invoked.
*
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(options) {
this._credential = undefined;
const tenantId = process.env.AZURE_TENANT_ID, clientId = process.env.AZURE_CLIENT_ID, clientSecret = process.env.AZURE_CLIENT_SECRET;
if (tenantId && clientId && clientSecret) {
this._credential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
}
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
if (this._credential) {
return this._credential.getToken(scopes, options);
}
return Promise.resolve(null);
}
}
// Copyright (c) Microsoft Corporation.
const DefaultScopeSuffix = "/.default";
const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
const ImdsApiVersion = "2018-02-01";
const AppServiceMsiApiVersion = "2017-09-01";
/**
* Attempts authentication using a managed identity that has been assigned
* to the deployment environment. This authentication type works in Azure VMs,
* App Service and Azure Functions applications, and inside of Azure Cloud Shell.
*
* More information about configuring managed identities can be found here:
*
* https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
*/
class ManagedIdentityCredential {
constructor(clientId, options) {
this.isEndpointUnavailable = null;
this.identityClient = new IdentityClient(options);
this.clientId = clientId;
}
mapScopesToResource(scopes) {

@@ -172,9 +359,2 @@ let scope = "";

}
dateInSeconds(date) {
return Math.floor(date.getTime() / 1000);
}
addMinutes(date, minutes) {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
createImdsAuthRequest(resource, clientId) {

@@ -246,6 +426,6 @@ const queryParameters = {

// returned quickly from the endpoint, proving its availability.
const webResource = this.createWebResource(request);
const webResource = this.identityClient.createWebResource(request);
webResource.timeout = 500;
try {
yield this.sendRequest(webResource);
yield this.identityClient.sendRequest(webResource);
}

@@ -262,23 +442,2 @@ catch (err) {

}
authenticateClientSecret(tenantId, clientId, clientSecret, scopes, getTokenOptions) {
const webResource = this.createWebResource({
url: `${this.baseUri}/${tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
});
return this.sendTokenRequest(webResource);
}
authenticateManagedIdentity(scopes, checkIfImdsEndpointAvailable, clientId, getTokenOptions) {

@@ -318,82 +477,8 @@ return tslib_1.__awaiter(this, void 0, void 0, function* () {

}
const webResource = this.createWebResource(Object.assign({ disableJsonStringifyOnBody: true, deserializationMapper: undefined, abortSignal: getTokenOptions && getTokenOptions.abortSignal }, authRequestOptions));
return this.sendTokenRequest(webResource, expiresInParser);
const webResource = this.identityClient.createWebResource(Object.assign({ disableJsonStringifyOnBody: true, deserializationMapper: undefined, abortSignal: getTokenOptions && getTokenOptions.abortSignal }, authRequestOptions));
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource, expiresInParser);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
authenticateClientCertificate(tenantId, clientId, certificateString, certificateX5t, scopes, getTokenOptions) {
const tokenId = uuid.v4();
const audienceUrl = `${this.baseUri}/${tenantId}/oauth2/v2.0/token`;
const header = {
typ: "JWT",
alg: "RS256",
x5t: certificateX5t
};
const payload = {
iss: clientId,
sub: clientId,
aud: audienceUrl,
jti: tokenId,
nbf: this.dateInSeconds(new Date()),
exp: this.dateInSeconds(this.addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: certificateString
});
const webResource = this.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
});
return this.sendTokenRequest(webResource);
}
static getDefaultOptions() {
return {
authorityHost: DefaultAuthorityHost
};
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory using a client secret
* that was generated for an App Registration. More information on how
* to configure a client secret can be found here:
*
* https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application
*
*/
class ClientSecretCredential {
/**
* Creates an instance of the ClientSecretCredential with the details
* needed to authenticate against Azure Active Directory with a client
* secret.
*
* @param tenantId The Azure Active Directory tenant (directory) ID.
* @param clientId The client (application) ID of an App Registration in the tenant.
* @param clientSecret A client secret that was generated for the App Registration.
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, clientSecret, options) {
this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this._clientSecret = clientSecret;
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if

@@ -409,83 +494,2 @@ * successful. If authentication cannot be performed at this time, this method may

getToken(scopes, options) {
return this.identityClient.authenticateClientSecret(this._tenantId, this._clientId, this._clientSecret, scopes, options);
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory using client secret
* details configured in the following environment variables:
*
* - AZURE_TENANT_ID: The Azure Active Directory tenant (directory) ID.
* - AZURE_CLIENT_ID: The client (application) ID of an App Registration in the tenant.
* - AZURE_CLIENT_SECRET: A client secret that was generated for the App Registration.
*
* This credential ultimately uses a {@link ClientSecretCredential} to
* perform the authentication using these details. Please consult the
* documentation of that class for more details.
*/
class EnvironmentCredential {
/**
* Creates an instance of the EnvironmentCredential class and reads
* client secret details from environment variables. If the expected
* environment variables are not found at this time, the getToken method
* will return null when invoked.
*
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(options) {
this._credential = undefined;
if (!coreHttp.isNode) {
throw "EnvironmentCredential is only supported when running in Node.js.";
}
const tenantId = process.env.AZURE_TENANT_ID, clientId = process.env.AZURE_CLIENT_ID, clientSecret = process.env.AZURE_CLIENT_SECRET;
if (tenantId && clientId && clientSecret) {
this._credential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
}
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
if (this._credential) {
return this._credential.getToken(scopes, options);
}
return Promise.resolve(null);
}
}
// Copyright (c) Microsoft Corporation.
/**
* Attempts authentication using a managed identity that has been assigned
* to the deployment environment. This authentication type works in Azure VMs,
* App Service and Azure Functions applications, and inside of Azure Cloud Shell.
*
* More information about configuring managed identities can be found here:
*
* https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
*/
class ManagedIdentityCredential {
constructor(clientId, options) {
this.isEndpointUnavailable = null;
this.identityClient = new IdentityClient(options);
this._clientId = clientId;
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {

@@ -498,3 +502,3 @@ let result = null;

result =
yield this.identityClient.authenticateManagedIdentity(scopes, this.isEndpointUnavailable === null, this._clientId, options);
yield this.authenticateManagedIdentity(scopes, this.isEndpointUnavailable === null, this.clientId, options);
// If authenticateManagedIdentity returns null, it means no MSI

@@ -534,2 +538,10 @@ // endpoints are available. In this case, don't try them in future

// Copyright (c) Microsoft Corporation.
const SelfSignedJwtLifetimeMins = 10;
function timestampInSeconds(date) {
return Math.floor(date.getTime() / 1000);
}
function addMinutes(date, minutes) {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
/**

@@ -555,7 +567,7 @@ * Enables authentication to Azure Active Directory using a PEM-encoded

this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this._certificateString = fs.readFileSync(certificatePath, "utf8");
this.tenantId = tenantId;
this.clientId = clientId;
this.certificateString = fs.readFileSync(certificatePath, "utf8");
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
const matchCert = this._certificateString.match(certificatePattern);
const matchCert = this.certificateString.match(certificatePattern);
const publicKey = matchCert ? matchCert[3] : "";

@@ -582,3 +594,45 @@ if (!publicKey) {

getToken(scopes, options) {
return this.identityClient.authenticateClientCertificate(this._tenantId, this._clientId, this._certificateString, this.certificateX5t, scopes, options);
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const tokenId = uuid.v4();
const audienceUrl = `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`;
const header = {
typ: "JWT",
alg: "RS256",
x5t: this.certificateX5t
};
const payload = {
iss: this.clientId,
sub: this.clientId,
aud: audienceUrl,
jti: tokenId,
nbf: timestampInSeconds(new Date()),
exp: timestampInSeconds(addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: this.certificateString
});
const webResource = this.identityClient.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}

@@ -588,2 +642,215 @@ }

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const BrowserNotSupportedError = new Error("InteractiveBrowserCredential is not supported in Node.js.");
/**
* Enables authentication to Azure Active Directory inside of the web browser
* using the interactive login flow, either via browser redirects or a popup
* window. This credential is not currently supported in Node.js.
*/
class InteractiveBrowserCredential {
constructor(tenantId, clientId, options) {
throw BrowserNotSupportedError;
}
getToken(scopes, options) {
throw BrowserNotSupportedError;
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory using a device code
* that the user can enter into https://microsoft.com/devicelogin.
*/
class DeviceCodeCredential {
/**
* Creates an instance of DeviceCodeCredential with the details needed
* to initiate the device code authorization flow with Azure Active Directory.
*
* @param tenantId The Azure Active Directory tenant (directory) ID or name.
* @param clientId The client (application) ID of an App Registration in the tenant.
* @param userPromptCallback A callback function that will be invoked to show
{@link DeviceCodeDetails} to the user.
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, userPromptCallback, options) {
this.lastTokenResponse = null;
this.identityClient = new IdentityClient(options);
this.tenantId = tenantId;
this.clientId = clientId;
this.userPromptCallback = userPromptCallback;
}
sendDeviceCodeRequest(scope, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/devicecode`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
client_id: this.clientId,
scope
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const response = yield this.identityClient.sendRequest(webResource);
if (!(response.status === 200 || response.status === 201)) {
throw new AuthenticationError(response.status, response.bodyAsText);
}
return response.parsedBody;
});
}
pollForToken(deviceCodeResponse, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
let tokenResponse = null;
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
client_id: this.clientId,
device_code: deviceCodeResponse.device_code
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
while (tokenResponse === null) {
try {
yield coreHttp.delay(deviceCodeResponse.interval * 1000);
// Check the abort signal before sending the request
if (options && options.abortSignal && options.abortSignal.aborted) {
return null;
}
tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
}
catch (err) {
if (err instanceof AuthenticationError) {
switch (err.errorResponse.error) {
case "authorization_pending":
break;
case "authorization_declined":
return null;
case "expired_token":
throw err;
case "bad_verification_code":
throw err;
}
}
else {
throw err;
}
}
}
return tokenResponse;
});
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
let tokenResponse = null;
let scopeString = typeof scopes === "string" ? scopes : scopes.join(" ");
if (scopeString.indexOf("offline_access") < 0) {
scopeString += " offline_access";
}
// Try to use the refresh token first
if (this.lastTokenResponse && this.lastTokenResponse.refreshToken) {
tokenResponse = yield this.identityClient.refreshAccessToken(this.tenantId, this.clientId, scopeString, this.lastTokenResponse.refreshToken, undefined, // clientSecret not needed for device code auth
undefined, options);
}
if (tokenResponse === null) {
const deviceCodeResponse = yield this.sendDeviceCodeRequest(scopeString, options);
this.userPromptCallback({
userCode: deviceCodeResponse.user_code,
verificationUri: deviceCodeResponse.verification_uri,
message: deviceCodeResponse.message
});
tokenResponse = yield this.pollForToken(deviceCodeResponse, options);
}
this.lastTokenResponse = tokenResponse;
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
}
// Copyright (c) Microsoft Corporation.
/**
* Enables authentication to Azure Active Directory with a user's
* username and password. This credential requires a high degree of
* trust so you should only use it when other, more secure credential
* types can't be used.
*/
class UsernamePasswordCredential {
/**
* Creates an instance of the UsernamePasswordCredential with the details
* needed to authenticate against Azure Active Directory with a username
* and password.
*
* @param tenantIdOrName The Azure Active Directory tenant (directory) ID or name.
* @param clientId The client (application) ID of an App Registration in the tenant.
* @param username The user account's e-mail address (user name).
* @param password The user account's account password
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(tenantIdOrName, clientId, username, password, options) {
this.identityClient = new IdentityClient(options);
this.tenantId = tenantIdOrName;
this.clientId = clientId;
this.username = username;
this.password = password;
}
/**
* Authenticates with Azure Active Directory and returns an {@link AccessToken} if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @param scopes The list of scopes for which the token will have access.
* @param options The options used to configure any requests this
* TokenCredential implementation might make.
*/
getToken(scopes, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "password",
client_id: this.clientId,
username: this.username,
password: this.password,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = yield this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
});
}
}
// Copyright (c) Microsoft Corporation.
function getDefaultAzureCredential() {

@@ -599,5 +866,8 @@ return new DefaultAzureCredential();

exports.DefaultAzureCredential = DefaultAzureCredential;
exports.DeviceCodeCredential = DeviceCodeCredential;
exports.EnvironmentCredential = EnvironmentCredential;
exports.InteractiveBrowserCredential = InteractiveBrowserCredential;
exports.ManagedIdentityCredential = ManagedIdentityCredential;
exports.UsernamePasswordCredential = UsernamePasswordCredential;
exports.getDefaultAzureCredential = getDefaultAzureCredential;
//# sourceMappingURL=index.js.map
{
"name": "@azure/identity",
"sdk-type": "client",
"version": "1.0.0-preview.1",
"version": "1.0.0-preview.2",
"description": "Provides credential implementations for Azure SDK libraries that can authenticate with Azure Active Directory",

@@ -11,2 +11,7 @@ "main": "dist/index.js",

"stream": "./node_modules/stream-browserify/index.js",
"./dist-esm/src/credentials/environmentCredential.js": "./dist-esm/src/credentials/environmentCredential.browser.js",
"./dist-esm/src/credentials/managedIdentityCredential.js": "./dist-esm/src/credentials/managedIdentityCredential.browser.js",
"./dist-esm/src/credentials/clientCertificateCredential.js": "./dist-esm/src/credentials/clientCertificateCredential.browser.js",
"./dist-esm/src/credentials/deviceCodeCredential.js": "./dist-esm/src/credentials/deviceCodeCredential.browser.js",
"./dist-esm/src/credentials/interactiveBrowserCredential.js": "./dist-esm/src/credentials/interactiveBrowserCredential.browser.js",
"./dist/index.js": "./browser/index.js"

@@ -21,6 +26,6 @@ },

"build:test:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c rollup.test.config.js 2>&1",
"build:test": "npm run build:test:node",
"build": "npm run build:node",
"build:test": "tsc -p . && rollup -c rollup.test.config.js 2>&1",
"build": "tsc -p . && rollup -c 2>&1",
"check-format": "prettier --list-different --config ../../.prettierrc.json \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"clean": "rimraf dist dist-esm test-dist typings *.tgz *.log",
"clean": "rimraf dist dist-esm browser test-dist test-browser typings *.tgz *.log",
"format": "prettier --write --config ../../.prettierrc.json \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",

@@ -37,3 +42,3 @@ "integration-test:browser": "echo skipped",

"test": "npm run build:test && npm run unit-test && npm run integration-test",
"unit-test:browser": "echo skipped",
"unit-test:browser": "karma start",
"unit-test:node": "mocha test-dist/**/*.js --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=-",

@@ -68,8 +73,9 @@ "unit-test": "npm run unit-test:node && npm run unit-test:browser"

},
"homepage": "https://github.com/azure/azure-sdk-for-js/tree/master/sdk/core/identity",
"homepage": "https://github.com/azure/azure-sdk-for-js/tree/master/sdk/identity/identity",
"sideEffects": false,
"dependencies": {
"@azure/core-http": "^1.0.0-preview.1",
"@azure/core-http": "1.0.0-preview.2",
"events": "^3.0.0",
"jws": "~3.2.2",
"msal": "~1.0.2",
"qs": "6.7.0",

@@ -80,2 +86,3 @@ "tslib": "^1.9.3",

"devDependencies": {
"@azure/abort-controller": "1.0.0-preview.1",
"@types/jws": "~3.2.0",

@@ -86,4 +93,4 @@ "@types/mocha": "^5.2.5",

"@types/uuid": "^3.4.3",
"@typescript-eslint/eslint-plugin": "~1.9.0",
"@typescript-eslint/parser": "^1.7.0",
"@typescript-eslint/eslint-plugin": "^1.11.0",
"@typescript-eslint/parser": "^1.11.0",
"assert": "^1.4.1",

@@ -93,2 +100,12 @@ "cross-env": "^5.2.0",

"inherits": "^2.0.3",
"karma": "^4.0.1",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage": "^1.1.2",
"karma-env-preprocessor": "^0.1.1",
"karma-json-preprocessor": "^0.3.3",
"karma-json-to-file-reporter": "^1.0.1",
"karma-junit-reporter": "^1.2.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-remap-coverage": "^0.1.5",
"mocha": "^5.2.0",

@@ -98,6 +115,7 @@ "mocha-junit-reporter": "^1.18.0",

"prettier": "^1.16.4",
"puppeteer": "^1.11.0",
"rimraf": "^2.6.2",
"rollup": "~1.13.1",
"rollup": "^1.16.3",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-json": "^3.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-multi-entry": "^2.1.0",

@@ -107,7 +125,7 @@ "rollup-plugin-node-resolve": "^5.0.2",

"rollup-plugin-sourcemaps": "^0.4.2",
"rollup-plugin-uglify": "^6.0.0",
"rollup-plugin-visualizer": "^1.0.0",
"rollup-plugin-terser": "^5.1.1",
"rollup-plugin-visualizer": "^2.0.0",
"typescript": "^3.2.2",
"util": "^0.11.1"
"util": "^0.12.1"
}
}

@@ -15,3 +15,3 @@ ## Azure Identity client library for JS

**NOTE:** The credential implementations in this library are not yet supported in the browser. We will provide browser-supported implementations for some in a future preview release.
**NOTE:** The credential implementations in this library are not yet supported in the browser. We will provide browser-supported implementations for some in a future preview release.

@@ -21,2 +21,3 @@ ### Install the package

Install Azure Identity with `npm`:
```sh

@@ -30,3 +31,3 @@ npm install --save @azure/identity

Azure Identity offers a variety of credential classes that are accepted by Azure SDK data plane clients. Each client library documents its Azure Identity integration in its README and samples. Azure SDK management plane libraries (those starting with `@azure/arm-*`)
Azure Identity offers a variety of credential classes that are accepted by Azure SDK data plane clients. Each client library documents its Azure Identity integration in its README and samples. Azure SDK management plane libraries (those starting with `@azure/arm-*`)
do not accept these credentials.

@@ -36,9 +37,12 @@

|credential class|identity|configuration
|-|-|-
|`DefaultAzureCredential`|service principal or managed identity|none for managed identity; [environment variables](#environment-variables) for service principal
|`ManagedIdentityCredential`|managed identity|none
|`EnvironmentCredential`|service principal|[environment variables](#environment-variables)
|`ClientSecretCredential`|service principal|constructor parameters
|`ClientCertificateCredential`|service principal|constructor parameters
| credential class | identity | configuration |
| ---------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [`DefaultAzureCredential`][1] | service principal or managed identity | none for managed identity; [environment variables](#environment-variables) for service principal |
| [`ManagedIdentityCredential`][2] | managed identity | none |
| [`EnvironmentCredential`][3] | service principal | [environment variables](#environment-variables) |
| [`ClientSecretCredential`][4] | service principal | constructor parameters |
| [`ClientCertificateCredential`][5] | service principal | constructor parameters |
| [`DeviceCodeCredential`][6] | app registration details | constructor parameters |
| [`InteractiveBrowserCredential`][7]| app registration details | constructor parameters |
| [`UsernamePasswordCredential`][8] | user principal | constructor parameters |

@@ -49,3 +53,3 @@ Credentials can be chained and tried in turn until one succeeds; see [chaining credentials](#chaining-credentials) for details.

`DefaultAzureCredential` is appropriate for most scenarios. It supports authenticating as a service principal or managed identity. To authenticate as a service principal, provide configuration in environment variables as described in the next section. Currently this credential attempts to use the `EnvironmentCredential` and `ManagedIdentityCredential`, in that order.
`DefaultAzureCredential` is appropriate for most scenarios. It supports authenticating as a service principal or managed identity. To authenticate as a service principal, provide configuration in environment variables as described in the next section. Currently this credential attempts to use the `EnvironmentCredential` and `ManagedIdentityCredential`, in that order.

@@ -58,7 +62,7 @@ Authenticating as a managed identity requires no configuration, but does require platform support. See the [managed identity documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities) for more information.

|variable name|value
|-|-
|`AZURE_CLIENT_ID`|service principal's app id
|`AZURE_TENANT_ID`|id of the principal's Azure Active Directory tenant
|`AZURE_CLIENT_SECRET`|one of the service principal's client secrets
| variable name | value |
| --------------------- | --------------------------------------------------- |
| `AZURE_CLIENT_ID` | service principal's app id |
| `AZURE_TENANT_ID` | id of the principal's Azure Active Directory tenant |
| `AZURE_CLIENT_SECRET` | one of the service principal's client secrets |

@@ -73,3 +77,3 @@ ## Examples

const { KeysClient } = require("@azure/keyvault-keys");
const { DefaultAzureCredential } = require('@azure/identity');
const { DefaultAzureCredential } = require("@azure/identity");

@@ -83,13 +87,18 @@ // Azure SDK clients accept the credential as a parameter

### Authenticating as a service principal:
```javascript
// Using a client secret
const { ClientSecretCredential } = require('@azure/identity');
const { ClientSecretCredential } = require("@azure/identity");
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
// Using a PEM-encoded certificate with a private key, not password protected
const { ClientCertificateCredential } = require('@azure/identity');
const credential = new ClientCertificateCredential(tenantId, clientId, "/app/certs/certificate.pem")
const { ClientCertificateCredential } = require("@azure/identity");
const credential = new ClientCertificateCredential(
tenantId,
clientId,
"/app/certs/certificate.pem"
);
// Using environment variables (see "Environment variables" above for variable names)
const { EnvironmentCredential } = require('@azure/identity');
const { EnvironmentCredential } = require("@azure/identity");
const credential = new EnvironmentCredential();

@@ -99,4 +108,5 @@ ```

### Chaining credentials:
```javascript
const { ClientSecretCredential, ChainedTokenCredential } = require('@azure/identity');
const { ClientSecretCredential, ChainedTokenCredential } = require("@azure/identity");

@@ -117,6 +127,11 @@ // When an access token is requested, the chain will try each

### General
Credentials raise `AuthenticationError` when they fail to authenticate. This class has a `message` field which describes why authentication failed. An `AggregateAuthenticationError` will be raised by `ChainedTokenCredential` with an `errors` field containing an array of errors from each credential in the chain.
Credentials raise `AuthenticationError` when they fail to authenticate. This class has a `message` field which describes why authentication failed. An `AggregateAuthenticationError` will be raised by `ChainedTokenCredential` with an `errors` field containing an array of errors from each credential in the chain.
## Next steps
### Read the documentation
API documentation for this library can be found on our [documentation site](https://azure.github.io/azure-sdk-for-js/identity/index.html).
### Provide Feedback

@@ -127,3 +142,4 @@

## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us

@@ -136,4 +152,15 @@ the rights to use your contribution. For details, visit [https://cla.microsoft.com](https://cla.microsoft.com).

If you'd like to contribute to this library, please read the [contributing guide](../../../CONTRIBUTING.md) to learn more about how to build and test the code.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
[1]: https://azure.github.io/azure-sdk-for-js/identity/classes/defaultazurecredential.html
[2]: https://azure.github.io/azure-sdk-for-js/identity/classes/managedidentitycredential.html
[3]: https://azure.github.io/azure-sdk-for-js/identity/classes/environmentcredential.html
[4]: https://azure.github.io/azure-sdk-for-js/identity/classes/clientsecretcredential.html
[5]: https://azure.github.io/azure-sdk-for-js/identity/classes/clientcertificatecredential.html
[6]: https://azure.github.io/azure-sdk-for-js/identity/classes/devicecodecredential.html
[7]: https://azure.github.io/azure-sdk-for-js/identity/classes/interactivebrowsercredential.html
[8]: https://azure.github.io/azure-sdk-for-js/identity/classes/usernamepasswordcredential.html

@@ -8,3 +8,3 @@ // Copyright (c) Microsoft Corporation.

* https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#error-response-1
*
*
* NOTE: This documentation is for v1 OAuth support but the same error

@@ -22,2 +22,8 @@ * response details still apply to v2.

function isErrorResponse(errorResponse: any): errorResponse is ErrorResponse {
return errorResponse &&
typeof errorResponse.error === "string" &&
typeof errorResponse.error_description === "string";
}
/**

@@ -32,3 +38,3 @@ * Provides details about a failure to authenticate with Azure Active

constructor(statusCode: number, errorBody: string | undefined | null) {
constructor(statusCode: number, errorBody: object | string | undefined | null) {
let errorResponse = {

@@ -39,3 +45,5 @@ error: "unknown",

if (errorBody) {
if (isErrorResponse(errorBody)) {
errorResponse = errorBody;
} else if (typeof errorBody === "string") {
try {

@@ -42,0 +50,0 @@ // Most error responses will contain JSON-formatted error details

@@ -5,4 +5,2 @@ // Copyright (c) Microsoft Corporation.

import qs from "qs";
import jws from "jws";
import uuid from "uuid";
import {

@@ -12,17 +10,29 @@ AccessToken,

ServiceClientOptions,
GetTokenOptions,
WebResource,
RequestPrepareOptions,
RestError
GetTokenOptions
} from "@azure/core-http";
import { AuthenticationError } from "./errors";
const SelfSignedJwtLifetimeMins = 10;
const DefaultAuthorityHost = "https://login.microsoftonline.com";
const DefaultScopeSuffix = "/.default";
export const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export const ImdsApiVersion = "2018-02-01";
export const AppServiceMsiApiVersion = "2017-09-01";
/**
* An internal type used to communicate details of a token request's
* response that should not be sent back as part of the AccessToken.
*/
export interface TokenResponse {
/**
* The AccessToken to be returned from getToken.
*/
accessToken: AccessToken,
/**
* The refresh token if the 'offline_access' scope was used.
*/
refreshToken?: string
}
export class IdentityClient extends ServiceClient {
public authorityHost: string;
constructor(options?: IdentityClientOptions) {

@@ -32,6 +42,10 @@ options = options || IdentityClient.getDefaultOptions();

this.baseUri = options.authorityHost;
this.baseUri = this.authorityHost = options.authorityHost || DefaultAuthorityHost;
if (!this.baseUri.startsWith("https:")) {
throw new Error("The authorityHost address must use the 'https' protocol.");
}
}
private createWebResource(requestOptions: RequestPrepareOptions): WebResource {
createWebResource(requestOptions: RequestPrepareOptions): WebResource {
const webResource = new WebResource();

@@ -42,6 +56,6 @@ webResource.prepare(requestOptions);

private async sendTokenRequest(
async sendTokenRequest(
webResource: WebResource,
expiresOnParser?: (responseBody: any) => number,
): Promise<AccessToken | null> {
): Promise<TokenResponse | null> {
const response = await this.sendRequest(webResource);

@@ -55,149 +69,43 @@

return {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
accessToken: {
token: response.parsedBody.access_token,
expiresOnTimestamp: expiresOnParser(response.parsedBody)
},
refreshToken: response.parsedBody.refresh_token,
};
} else {
throw new AuthenticationError(response.status, response.bodyAsText);
throw new AuthenticationError(response.status, response.parsedBody || response.bodyAsText);
}
}
private mapScopesToResource(scopes: string | string[]): string {
let scope = "";
if (Array.isArray(scopes)) {
if (scopes.length !== 1) {
throw "To convert to a resource string the specified array must be exactly length 1";
}
scope = scopes[0];
} else if (typeof scopes === "string") {
scope = scopes;
async refreshAccessToken(
tenantId: string,
clientId: string,
scopes: string,
refreshToken: string | undefined,
clientSecret: string | undefined,
expiresOnParser?: (responseBody: any) => number,
options?: GetTokenOptions
): Promise<TokenResponse | null> {
if (refreshToken === undefined) {
return null;
}
if (!scope.endsWith(DefaultScopeSuffix)) {
return scope;
}
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
}
private dateInSeconds(date: Date): number {
return Math.floor(date.getTime() / 1000);
}
private addMinutes(date: Date, minutes: number): Date {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
private createImdsAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const queryParameters: any = {
resource,
"api-version": ImdsApiVersion
const refreshParams = {
grant_type: "refresh_token",
client_id: clientId,
refresh_token: refreshToken,
scope: scopes
};
if (clientId) {
queryParameters.client_id = clientId;
if (clientSecret !== undefined) {
(refreshParams as any).client_secret = clientSecret;
}
return {
url: ImdsEndpoint,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
Metadata: true
}
};
}
private createAppServiceMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const queryParameters: any = {
resource,
"api-version": AppServiceMsiApiVersion,
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
secret: process.env.MSI_SECRET
}
};
}
private createCloudShellMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const body: any = {
resource
};
if (clientId) {
body.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "POST",
body: qs.stringify(body),
headers: {
Accept: "application/json",
Metadata: true,
"Content-Type": "application/x-www-form-urlencoded"
}
};
}
private async pingImdsEndpoint(resource: string, clientId?: string): Promise<boolean> {
const request = this.createImdsAuthRequest(resource, clientId);
// This will always be populated, but let's make TypeScript happy
if (request.headers) {
// Remove the Metadata header to invoke a request error from
// IMDS endpoint
delete request.headers.Metadata;
}
// Create a request with a 500 msec timeout since we expect that
// not having a "Metadata" header should cause an error to be
// returned quickly from the endpoint, proving its availability.
const webResource = this.createWebResource(request);
webResource.timeout = 500;
try {
await this.sendRequest(webResource);
} catch (err) {
if (err instanceof RestError && err.code === RestError.REQUEST_SEND_ERROR) {
// Either request failed or IMDS endpoint isn't available
return false;
}
}
// If we received any response, the endpoint is available
return true;
}
authenticateClientSecret(
tenantId: string,
clientId: string,
clientSecret: string,
scopes: string | string[],
getTokenOptions?: GetTokenOptions
): Promise<AccessToken | null> {
const webResource = this.createWebResource({
url: `${this.baseUri}/${tenantId}/oauth2/v2.0/token`,
url: `${this.authorityHost}/${tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
body: qs.stringify(refreshParams),
headers: {

@@ -207,109 +115,19 @@ Accept: "application/json",

},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
abortSignal: options && options.abortSignal
});
return this.sendTokenRequest(webResource);
}
async authenticateManagedIdentity(
scopes: string | string[],
checkIfImdsEndpointAvailable: boolean,
clientId?: string,
getTokenOptions?: GetTokenOptions
): Promise<AccessToken | null> {
let authRequestOptions: RequestPrepareOptions;
const resource = this.mapScopesToResource(scopes);
let expiresInParser: ((requestBody: any) => number) | undefined;
// Detect which type of environment we are running in
if (process.env.MSI_ENDPOINT) {
if (process.env.MSI_SECRET) {
// Running in App Service
authRequestOptions = this.createAppServiceMsiAuthRequest(resource, clientId);
expiresInParser = (requestBody: any) => {
// Parse a date format like "06/20/2019 02:57:58 +00:00" and
// convert it into a JavaScript-formatted date
const m = requestBody.expires_on.match(/(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d) (\+|-)(\d\d):(\d\d)/)
return Date.parse(`${m[3]}-${m[1]}-${m[2]}T${m[4]}:${m[5]}:${m[6]}${m[7]}${m[8]}:${m[9]}`)
};
try {
return await this.sendTokenRequest(webResource, expiresOnParser);
} catch (err) {
if (err instanceof AuthenticationError && err.errorResponse.error === "interaction_required") {
// It's likely that the refresh token has expired, so
// return null so that the credential implementation will
// initiate the authentication flow again.
return null;
} else {
// Running in Cloud Shell
authRequestOptions = this.createCloudShellMsiAuthRequest(resource, clientId);
throw err;
}
} else {
// Ping the IMDS endpoint to see if it's available
if (!checkIfImdsEndpointAvailable || await this.pingImdsEndpoint(resource, clientId)) {
// Running in an Azure VM
authRequestOptions = this.createImdsAuthRequest(resource, clientId);
} else {
// Returning null tells the ManagedIdentityCredential that
// no MSI authentication endpoints are available
return null;
}
}
const webResource = this.createWebResource({
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
abortSignal: getTokenOptions && getTokenOptions.abortSignal,
...authRequestOptions
});
return this.sendTokenRequest(webResource, expiresInParser);
}
authenticateClientCertificate(
tenantId: string,
clientId: string,
certificateString: string,
certificateX5t: string,
scopes: string | string[],
getTokenOptions?: GetTokenOptions
): Promise<AccessToken | null> {
const tokenId = uuid.v4();
const audienceUrl = `${this.baseUri}/${tenantId}/oauth2/v2.0/token`;
const header: jws.Header = {
typ: "JWT",
alg: "RS256",
x5t: certificateX5t
};
const payload = {
iss: clientId,
sub: clientId,
aud: audienceUrl,
jti: tokenId,
nbf: this.dateInSeconds(new Date()),
exp: this.dateInSeconds(this.addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: certificateString
});
const webResource = this.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: getTokenOptions && getTokenOptions.abortSignal
});
return this.sendTokenRequest(webResource);
}
static getDefaultOptions(): IdentityClientOptions {

@@ -323,3 +141,3 @@ return {

export interface IdentityClientOptions extends ServiceClientOptions {
authorityHost: string;
authorityHost?: string;
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import qs from "qs";
import jws from "jws";
import uuid from "uuid";
import { readFileSync } from "fs";

@@ -9,2 +12,13 @@ import { createHash } from "crypto";

const SelfSignedJwtLifetimeMins = 10;
function timestampInSeconds(date: Date): number {
return Math.floor(date.getTime() / 1000);
}
function addMinutes(date: Date, minutes: number): Date {
date.setMinutes(date.getMinutes() + minutes);
return date;
}
/**

@@ -14,3 +28,3 @@ * Enables authentication to Azure Active Directory using a PEM-encoded

* on how to configure certificate authentication can be found here:
*
*
* https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials#register-your-certificate-with-azure-ad

@@ -21,13 +35,12 @@ *

private identityClient: IdentityClient;
private _tenantId: string;
private _clientId: string;
private _certificateString: string;
private tenantId: string;
private clientId: string;
private certificateString: string;
private certificateThumbprint: string;
private certificateX5t: string;
public certificateThumbprint: string;
public certificateX5t: string;
/**
* Creates an instance of the ClientCertificateCredential with the details
* needed to authenticate against Azure Active Directory with a certificate.
*
*
* @param tenantId The Azure Active Directory tenant (directory) ID.

@@ -45,9 +58,9 @@ * @param clientId The client (application) ID of an App Registration in the tenant.

this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this.tenantId = tenantId;
this.clientId = clientId;
this._certificateString = readFileSync(certificatePath, "utf8");
this.certificateString = readFileSync(certificatePath, "utf8");
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
const matchCert = this._certificateString.match(certificatePattern);
const matchCert = this.certificateString.match(certificatePattern);
const publicKey = matchCert ? matchCert[3] : "";

@@ -73,3 +86,3 @@ if (!publicKey) {

* containing failure details will be thrown.
*
*
* @param scopes The list of scopes for which the token will have access.

@@ -79,15 +92,52 @@ * @param options The options used to configure any requests this

*/
public getToken(
public async getToken(
scopes: string | string[],
options?: GetTokenOptions
): Promise<AccessToken | null> {
return this.identityClient.authenticateClientCertificate(
this._tenantId,
this._clientId,
this._certificateString,
this.certificateX5t,
scopes,
options
);
const tokenId = uuid.v4();
const audienceUrl = `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`;
const header: jws.Header = {
typ: "JWT",
alg: "RS256",
x5t: this.certificateX5t
};
const payload = {
iss: this.clientId,
sub: this.clientId,
aud: audienceUrl,
jti: tokenId,
nbf: timestampInSeconds(new Date()),
exp: timestampInSeconds(addMinutes(new Date(), SelfSignedJwtLifetimeMins))
};
const clientAssertion = jws.sign({
header,
payload,
secret: this.certificateString
});
const webResource = this.identityClient.createWebResource({
url: audienceUrl,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = await this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import qs from "qs";
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";

@@ -11,3 +12,3 @@ import { IdentityClientOptions, IdentityClient } from "../client/identityClient";

* to configure a client secret can be found here:
*
*
* https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application

@@ -18,5 +19,5 @@ *

private identityClient: IdentityClient;
private _tenantId: string;
private _clientId: string;
private _clientSecret: string;
private tenantId: string;
private clientId: string;
private clientSecret: string;

@@ -27,3 +28,3 @@ /**

* secret.
*
*
* @param tenantId The Azure Active Directory tenant (directory) ID.

@@ -41,5 +42,5 @@ * @param clientId The client (application) ID of an App Registration in the tenant.

this.identityClient = new IdentityClient(options);
this._tenantId = tenantId;
this._clientId = clientId;
this._clientSecret = clientSecret;
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}

@@ -52,3 +53,3 @@

* containing failure details will be thrown.
*
*
* @param scopes The list of scopes for which the token will have access.

@@ -58,14 +59,28 @@ * @param options The options used to configure any requests this

*/
public getToken(
public async getToken(
scopes: string | string[],
options?: GetTokenOptions
): Promise<AccessToken | null> {
return this.identityClient.authenticateClientSecret(
this._tenantId,
this._clientId,
this._clientSecret,
scopes,
options
);
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
response_type: "token",
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal
});
const tokenResponse = await this.identityClient.sendTokenRequest(webResource);
return (tokenResponse && tokenResponse.accessToken) || null;
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AccessToken, TokenCredential, isNode, GetTokenOptions } from "@azure/core-http";
import { AccessToken, TokenCredential, GetTokenOptions } from "@azure/core-http";
import { IdentityClientOptions } from "../client/identityClient";

@@ -11,7 +11,7 @@ import { ClientSecretCredential } from "./clientSecretCredential";

* details configured in the following environment variables:
*
*
* - AZURE_TENANT_ID: The Azure Active Directory tenant (directory) ID.
* - AZURE_CLIENT_ID: The client (application) ID of an App Registration in the tenant.
* - AZURE_CLIENT_SECRET: A client secret that was generated for the App Registration.
*
*
* This credential ultimately uses a {@link ClientSecretCredential} to

@@ -28,10 +28,6 @@ * perform the authentication using these details. Please consult the

* will return null when invoked.
*
*
* @param options Options for configuring the client which makes the authentication request.
*/
constructor(options?: IdentityClientOptions) {
if (!isNode) {
throw "EnvironmentCredential is only supported when running in Node.js.";
}
const tenantId = process.env.AZURE_TENANT_ID,

@@ -51,3 +47,3 @@ clientId = process.env.AZURE_CLIENT_ID,

* containing failure details will be thrown.
*
*
* @param scopes The list of scopes for which the token will have access.

@@ -54,0 +50,0 @@ * @param options The options used to configure any requests this

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-http";
import qs from "qs";
import {
AccessToken,
GetTokenOptions,
RequestPrepareOptions,
RestError,
TokenCredential
} from "@azure/core-http";
import { IdentityClientOptions, IdentityClient } from "../client/identityClient";
const DefaultScopeSuffix = "/.default";
export const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
export const ImdsApiVersion = "2018-02-01";
export const AppServiceMsiApiVersion = "2017-09-01";
/**

@@ -11,5 +23,5 @@ * Attempts authentication using a managed identity that has been assigned

* App Service and Azure Functions applications, and inside of Azure Cloud Shell.
*
*
* More information about configuring managed identities can be found here:
*
*
* https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview

@@ -19,3 +31,3 @@ */

private identityClient: IdentityClient;
private _clientId: string | undefined;
private clientId: string | undefined;
private isEndpointUnavailable: boolean | null = null;

@@ -25,5 +37,164 @@

this.identityClient = new IdentityClient(options);
this._clientId = clientId;
this.clientId = clientId;
}
private mapScopesToResource(scopes: string | string[]): string {
let scope = "";
if (Array.isArray(scopes)) {
if (scopes.length !== 1) {
throw "To convert to a resource string the specified array must be exactly length 1";
}
scope = scopes[0];
} else if (typeof scopes === "string") {
scope = scopes;
}
if (!scope.endsWith(DefaultScopeSuffix)) {
return scope;
}
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
}
private createImdsAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const queryParameters: any = {
resource,
"api-version": ImdsApiVersion
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: ImdsEndpoint,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
Metadata: true
}
};
}
private createAppServiceMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const queryParameters: any = {
resource,
"api-version": AppServiceMsiApiVersion,
};
if (clientId) {
queryParameters.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
secret: process.env.MSI_SECRET
}
};
}
private createCloudShellMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
const body: any = {
resource
};
if (clientId) {
body.client_id = clientId;
}
return {
url: process.env.MSI_ENDPOINT,
method: "POST",
body: qs.stringify(body),
headers: {
Accept: "application/json",
Metadata: true,
"Content-Type": "application/x-www-form-urlencoded"
}
};
}
private async pingImdsEndpoint(resource: string, clientId?: string): Promise<boolean> {
const request = this.createImdsAuthRequest(resource, clientId);
// This will always be populated, but let's make TypeScript happy
if (request.headers) {
// Remove the Metadata header to invoke a request error from
// IMDS endpoint
delete request.headers.Metadata;
}
// Create a request with a 500 msec timeout since we expect that
// not having a "Metadata" header should cause an error to be
// returned quickly from the endpoint, proving its availability.
const webResource = this.identityClient.createWebResource(request);
webResource.timeout = 500;
try {
await this.identityClient.sendRequest(webResource);
} catch (err) {
if (err instanceof RestError && err.code === RestError.REQUEST_SEND_ERROR) {
// Either request failed or IMDS endpoint isn't available
return false;
}
}
// If we received any response, the endpoint is available
return true;
}
private async authenticateManagedIdentity(
scopes: string | string[],
checkIfImdsEndpointAvailable: boolean,
clientId?: string,
getTokenOptions?: GetTokenOptions
): Promise<AccessToken | null> {
let authRequestOptions: RequestPrepareOptions;
const resource = this.mapScopesToResource(scopes);
let expiresInParser: ((requestBody: any) => number) | undefined;
// Detect which type of environment we are running in
if (process.env.MSI_ENDPOINT) {
if (process.env.MSI_SECRET) {
// Running in App Service
authRequestOptions = this.createAppServiceMsiAuthRequest(resource, clientId);
expiresInParser = (requestBody: any) => {
// Parse a date format like "06/20/2019 02:57:58 +00:00" and
// convert it into a JavaScript-formatted date
const m = requestBody.expires_on.match(/(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d) (\+|-)(\d\d):(\d\d)/)
return Date.parse(`${m[3]}-${m[1]}-${m[2]}T${m[4]}:${m[5]}:${m[6]}${m[7]}${m[8]}:${m[9]}`)
};
} else {
// Running in Cloud Shell
authRequestOptions = this.createCloudShellMsiAuthRequest(resource, clientId);
}
} else {
// Ping the IMDS endpoint to see if it's available
if (!checkIfImdsEndpointAvailable || await this.pingImdsEndpoint(resource, clientId)) {
// Running in an Azure VM
authRequestOptions = this.createImdsAuthRequest(resource, clientId);
} else {
// Returning null tells the ManagedIdentityCredential that
// no MSI authentication endpoints are available
return null;
}
}
const webResource = this.identityClient.createWebResource({
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
abortSignal: getTokenOptions && getTokenOptions.abortSignal,
...authRequestOptions
});
const tokenResponse = await this.identityClient.sendTokenRequest(webResource, expiresInParser);
return (tokenResponse && tokenResponse.accessToken) || null;
}
/**

@@ -34,3 +205,3 @@ * Authenticates with Azure Active Directory and returns an {@link AccessToken} if

* containing failure details will be thrown.
*
*
* @param scopes The list of scopes for which the token will have access.

@@ -51,6 +222,6 @@ * @param options The options used to configure any requests this

result =
await this.identityClient.authenticateManagedIdentity(
await this.authenticateManagedIdentity(
scopes,
this.isEndpointUnavailable === null,
this._clientId,
this.clientId,
options);

@@ -57,0 +228,0 @@

@@ -12,4 +12,8 @@ // Copyright (c) Microsoft Corporation.

export { ClientCertificateCredential } from "./credentials/clientCertificateCredential";
export { InteractiveBrowserCredential } from "./credentials/interactiveBrowserCredential";
export { InteractiveBrowserCredentialOptions, BrowserLoginStyle } from "./credentials/interactiveBrowserCredentialOptions";
export { ManagedIdentityCredential } from "./credentials/managedIdentityCredential";
export { DeviceCodeCredential } from "./credentials/deviceCodeCredential";
export { DefaultAzureCredential } from "./credentials/defaultAzureCredential";
export { UsernamePasswordCredential } from "./credentials/usernamePasswordCredential";
export { AuthenticationError, AggregateAuthenticationError } from "./client/errors";

@@ -16,0 +20,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc