Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Msal Node adds support proxy #4447

Merged
merged 20 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0b95795
initial commit
Robbie-Microsoft Oct 15, 2021
63ff58f
https-proxy-agent changes to support proxy in msal-node
sameerag Oct 18, 2021
8b5a8b4
Merge branch 'dev' into msal-node-2600-v2
Robbie-Microsoft Nov 4, 2021
7585c6a
Merge branch 'dev' into msal-node-2600-v2
sameerag Jan 25, 2022
b026841
Merge branch 'msal-node-2600-v2' of https://github.com/AzureAD/micros…
Robbie-Microsoft Jan 25, 2022
a6fda9f
Change files
sameerag Jan 25, 2022
5621084
Merge branch 'msal-node-2600-v2' of https://github.com/AzureAD/micros…
Robbie-Microsoft Jan 25, 2022
c3fd220
added support for sendGetRequestAsync. Implemented missing support fo…
Robbie-Microsoft Feb 4, 2022
89f189e
Merge branch 'dev' into msal-node-2600-v2
sameerag Feb 4, 2022
88211a1
Implemented missing support for sendGetRequestAsync for region discov…
Robbie-Microsoft Feb 4, 2022
8e0c6d9
Merge branch 'dev' into msal-node-2600-v2
Robbie-Microsoft Feb 7, 2022
30e6adb
Implemented Thomas's Feedback
Robbie-Microsoft Feb 7, 2022
ae4ef42
fixed bug preventing jobs from completing
Robbie-Microsoft Feb 7, 2022
20d3d65
fixed lint errors
Robbie-Microsoft Feb 7, 2022
ecb11e3
Implemented Thomas's Feedback
Robbie-Microsoft Feb 7, 2022
c88341d
removed unused constant
Robbie-Microsoft Feb 7, 2022
648cffc
updated docs to include info about proxy bug fix
Robbie-Microsoft Feb 7, 2022
25d5af6
Merge branch 'dev' into msal-node-2600-v2
Robbie-Microsoft Feb 7, 2022
9c97b37
Implemented Sameera's feedback
Robbie-Microsoft Feb 7, 2022
47cc372
Merge branch 'dev' into msal-node-2600-v2
sameerag Feb 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Support proxy in msal-node(#4447)",
"packageName": "@azure/msal-common",
"email": "sameera.gajjarapu@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Support proxy in msal-node(#4447)",
"packageName": "@azure/msal-node",
"email": "sameera.gajjarapu@microsoft.com",
"dependentChangeType": "patch"
}
21 changes: 17 additions & 4 deletions lib/msal-common/src/authority/Authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@ export class Authority {
private regionDiscovery: RegionDiscovery;
// Region discovery metadata
public regionDiscoveryMetadata: RegionDiscoveryMetadata;
// Proxy url string
private proxyUrl: string;

constructor(authority: string, networkInterface: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions) {
constructor(authority: string, networkInterface: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions, proxyUrl: string) {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
this.canonicalAuthority = authority;
this._canonicalAuthority.validateAsUri();
this.networkInterface = networkInterface;
this.cacheManager = cacheManager;
this.authorityOptions = authorityOptions;
this.regionDiscovery = new RegionDiscovery(networkInterface);
this.regionDiscoveryMetadata = { region_used: undefined, region_source: undefined, region_outcome: undefined };
this.proxyUrl = proxyUrl;
}

// See above for AuthorityType
Expand Down Expand Up @@ -271,7 +274,7 @@ export class Authority {
if (metadata) {
// If the user prefers to use an azure region replace the global endpoints with regional information.
if (this.authorityOptions.azureRegionConfiguration?.azureRegion) {
const autodetectedRegionName = await this.regionDiscovery.detectRegion(this.authorityOptions.azureRegionConfiguration.environmentRegion, this.regionDiscoveryMetadata);
const autodetectedRegionName = await this.regionDiscovery.detectRegion(this.authorityOptions.azureRegionConfiguration.environmentRegion, this.regionDiscoveryMetadata, this.proxyUrl);

const azureRegion = this.authorityOptions.azureRegionConfiguration.azureRegion === Constants.AZURE_REGION_AUTO_DISCOVER_FLAG
? autodetectedRegionName
Expand Down Expand Up @@ -335,8 +338,13 @@ export class Authority {
* Gets OAuth endpoints from the given OpenID configuration endpoint.
*/
private async getEndpointMetadataFromNetwork(): Promise<OpenIdConfigResponse | null> {
const options = {};
if (this.proxyUrl.length) {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
options["proxyUrl"] = this.proxyUrl;
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
}

try {
const response = await this.networkInterface.sendGetRequestAsync<OpenIdConfigResponse>(this.defaultOpenIdConfigurationEndpoint);
const response = await this.networkInterface.sendGetRequestAsync<OpenIdConfigResponse>(this.defaultOpenIdConfigurationEndpoint, options);
return isOpenIdConfigResponse(response.body) ? response.body : null;
} catch (e) {
return null;
Expand Down Expand Up @@ -402,9 +410,14 @@ export class Authority {
*/
private async getCloudDiscoveryMetadataFromNetwork(): Promise<CloudDiscoveryMetadata | null> {
const instanceDiscoveryEndpoint = `${Constants.AAD_INSTANCE_DISCOVERY_ENDPT}${this.canonicalAuthority}oauth2/v2.0/authorize`;
const options = {};
if (this.proxyUrl.length) {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
options["proxyUrl"] = this.proxyUrl;
}

let match = null;
try {
const response = await this.networkInterface.sendGetRequestAsync<CloudInstanceDiscoveryResponse>(instanceDiscoveryEndpoint);
const response = await this.networkInterface.sendGetRequestAsync<CloudInstanceDiscoveryResponse>(instanceDiscoveryEndpoint, options);
const metadata = isCloudInstanceDiscoveryResponse(response.body) ? response.body.metadata : [];
if (metadata.length === 0) {
// If no metadata is returned, authority is untrusted
Expand Down
8 changes: 4 additions & 4 deletions lib/msal-common/src/authority/AuthorityFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export class AuthorityFactory {
* @param networkClient
* @param protocolMode
*/
static async createDiscoveredInstance(authorityUri: string, networkClient: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions): Promise<Authority> {
static async createDiscoveredInstance(authorityUri: string, networkClient: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions, proxyUrl: string): Promise<Authority> {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
// Initialize authority and perform discovery endpoint check.
const acquireTokenAuthority: Authority = AuthorityFactory.createInstance(authorityUri, networkClient, cacheManager, authorityOptions);
const acquireTokenAuthority: Authority = AuthorityFactory.createInstance(authorityUri, networkClient, cacheManager, authorityOptions, proxyUrl);

try {
await acquireTokenAuthority.resolveEndpointsAsync();
Expand All @@ -45,12 +45,12 @@ export class AuthorityFactory {
* @param networkInterface
* @param protocolMode
*/
static createInstance(authorityUrl: string, networkInterface: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions): Authority {
static createInstance(authorityUrl: string, networkInterface: INetworkModule, cacheManager: ICacheManager, authorityOptions: AuthorityOptions, proxyUrl: string): Authority {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
// Throw error if authority url is empty
if (StringUtils.isEmpty(authorityUrl)) {
throw ClientConfigurationError.createUrlEmptyError();
}

return new Authority(authorityUrl, networkInterface, cacheManager, authorityOptions);
return new Authority(authorityUrl, networkInterface, cacheManager, authorityOptions, proxyUrl);
}
}
21 changes: 13 additions & 8 deletions lib/msal-common/src/authority/RegionDiscovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,33 @@ export class RegionDiscovery {
*
* @returns Promise<string | null>
*/
public async detectRegion(environmentRegion: string | undefined, regionDiscoveryMetadata: RegionDiscoveryMetadata): Promise<string | null> {
public async detectRegion(environmentRegion: string | undefined, regionDiscoveryMetadata: RegionDiscoveryMetadata, proxyUrl: string): Promise<string | null> {
// Initialize auto detected region with the region from the envrionment
let autodetectedRegionName = environmentRegion;

// Check if a region was detected from the environment, if not, attempt to get the region from IMDS
if (!autodetectedRegionName) {
const options = RegionDiscovery.IMDS_OPTIONS;
if (proxyUrl.length) {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
options["proxyUrl"] = proxyUrl;
}

try {
const localIMDSVersionResponse = await this.getRegionFromIMDS(Constants.IMDS_VERSION);
const localIMDSVersionResponse = await this.getRegionFromIMDS(Constants.IMDS_VERSION, options);
if (localIMDSVersionResponse.status === ResponseCodes.httpSuccess) {
autodetectedRegionName = localIMDSVersionResponse.body;
regionDiscoveryMetadata.region_source = RegionDiscoverySources.IMDS;
}

// If the response using the local IMDS version failed, try to fetch the current version of IMDS and retry.
if (localIMDSVersionResponse.status === ResponseCodes.httpBadRequest) {
const currentIMDSVersion = await this.getCurrentVersion();
const currentIMDSVersion = await this.getCurrentVersion(options);
if (!currentIMDSVersion) {
regionDiscoveryMetadata.region_source = RegionDiscoverySources.FAILED_AUTO_DETECTION;
return null;
}

const currentIMDSVersionResponse = await this.getRegionFromIMDS(currentIMDSVersion);
const currentIMDSVersionResponse = await this.getRegionFromIMDS(currentIMDSVersion, options);
if (currentIMDSVersionResponse.status === ResponseCodes.httpSuccess) {
autodetectedRegionName = currentIMDSVersionResponse.body;
regionDiscoveryMetadata.region_source = RegionDiscoverySources.IMDS;
Expand Down Expand Up @@ -73,18 +78,18 @@ export class RegionDiscovery {
* @param imdsEndpointUrl
* @returns Promise<NetworkResponse<string>>
*/
private async getRegionFromIMDS(version: string): Promise<NetworkResponse<string>> {
return this.networkInterface.sendGetRequestAsync<string>(`${Constants.IMDS_ENDPOINT}?api-version=${version}&format=text`, RegionDiscovery.IMDS_OPTIONS, Constants.IMDS_TIMEOUT);
private async getRegionFromIMDS(version: string, options: object): Promise<NetworkResponse<string>> {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
return this.networkInterface.sendGetRequestAsync<string>(`${Constants.IMDS_ENDPOINT}?api-version=${version}&format=text`, options, Constants.IMDS_TIMEOUT);
}

/**
* Get the most recent version of the IMDS endpoint available
*
* @returns Promise<string | null>
*/
private async getCurrentVersion(): Promise<string | null> {
private async getCurrentVersion(options: object): Promise<string | null> {
try {
const response = await this.networkInterface.sendGetRequestAsync<IMDSBadResponse>(`${Constants.IMDS_ENDPOINT}?format=json`, RegionDiscovery.IMDS_OPTIONS);
const response = await this.networkInterface.sendGetRequestAsync<IMDSBadResponse>(`${Constants.IMDS_ENDPOINT}?format=json`, options);

// When IMDS endpoint is called without the api version query param, bad request response comes back with latest version.
if (response.status === ResponseCodes.httpBadRequest && response.body && response.body["newest-versions"] && response.body["newest-versions"].length > 0) {
Expand Down
4 changes: 2 additions & 2 deletions lib/msal-common/src/client/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export abstract class BaseClient {
const response = await this.networkManager.sendPostRequest<ServerAuthorizationTokenResponse>(
thumbprint,
tokenEndpoint,
{ body: queryString, headers: headers }
{ body: queryString, headers: headers, proxyUrl: this.config.systemOptions.proxyUrl }
);

if (this.config.serverTelemetryManager && response.status < 500 && response.status !== 429) {
Expand All @@ -122,7 +122,7 @@ export abstract class BaseClient {

/**
* Updates the authority object of the client. Endpoint discovery must be completed.
* @param updatedAuthority
* @param updatedAuthority
*/
updateAuthority(updatedAuthority: Authority): void {
if (!updatedAuthority.discoveryComplete()) {
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-common/src/client/DeviceCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export class DeviceCodeClient extends BaseClient {
deviceCodeEndpoint,
{
body: queryString,
headers: headers
headers: headers,
proxyUrl: this.config.systemOptions.proxyUrl
});

return {
Expand Down
8 changes: 5 additions & 3 deletions lib/msal-common/src/config/ClientConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export type AuthOptions = {
export type SystemOptions = {
tokenRenewalOffsetSeconds?: number;
preventCorsPreflight?: boolean;
proxyUrl?: string;
};

/**
Expand Down Expand Up @@ -123,7 +124,8 @@ export type ClientCredentials = {

export const DEFAULT_SYSTEM_OPTIONS: Required<SystemOptions> = {
tokenRenewalOffsetSeconds: DEFAULT_TOKEN_RENEWAL_OFFSET_SEC,
preventCorsPreflight: false
preventCorsPreflight: false,
proxyUrl: "",
};

const DEFAULT_LOGGER_IMPLEMENTATION: Required<LoggerOptions> = {
Expand Down Expand Up @@ -179,9 +181,9 @@ export function buildClientConfiguration(
persistencePlugin: persistencePlugin,
serializableCache: serializableCache
}: ClientConfiguration): CommonClientConfiguration {

const loggerOptions = { ...DEFAULT_LOGGER_IMPLEMENTATION, ...userLoggerOption };

return {
authOptions: buildAuthOptions(userAuthOptions),
systemOptions: { ...DEFAULT_SYSTEM_OPTIONS, ...userSystemOptions },
Expand Down
1 change: 1 addition & 0 deletions lib/msal-common/src/network/INetworkModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NetworkResponse } from "./NetworkManager";
export type NetworkRequestOptions = {
headers?: Record<string, string>,
body?: string;
proxyUrl?: string;
};

/**
Expand Down
16 changes: 8 additions & 8 deletions lib/msal-common/src/url/UrlString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class UrlString {
public get urlString(): string {
return this._urlString;
}

constructor(url: string) {
this._urlString = url;
if (StringUtils.isEmpty(this._urlString)) {
Expand All @@ -35,7 +35,7 @@ export class UrlString {

/**
* Ensure urls are lower case and end with a / character.
* @param url
* @param url
*/
static canonicalizeUri(url: string): string {
if (url) {
Expand Down Expand Up @@ -82,8 +82,8 @@ export class UrlString {

/**
* Given a url and a query string return the url with provided query string appended
* @param url
* @param queryString
* @param url
* @param queryString
*/
static appendQueryString(url: string, queryString: string): string {
if (StringUtils.isEmpty(queryString)) {
Expand All @@ -95,7 +95,7 @@ export class UrlString {

/**
* Returns a url with the hash removed
* @param url
* @param url
*/
static removeHashFromUrl(url: string): string {
return UrlString.canonicalizeUri(url.split("#")[0]);
Expand Down Expand Up @@ -173,13 +173,13 @@ export class UrlString {

return baseComponents.Protocol + "//" + baseComponents.HostNameAndPort + relativeUrl;
}

return relativeUrl;
}

/**
* Parses hash string from given string. Returns empty string if no hash symbol is found.
* @param hashString
* @param hashString
*/
static parseHash(hashString: string): string {
const hashIndex1 = hashString.indexOf("#");
Expand Down
17 changes: 17 additions & 0 deletions lib/msal-node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/msal-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"dependencies": {
"@azure/msal-common": "^6.0.0",
"axios": "^0.21.4",
"https-proxy-agent": "^5.0.0",
"jsonwebtoken": "^8.5.1",
"uuid": "^8.3.0"
},
Expand Down
5 changes: 4 additions & 1 deletion lib/msal-node/src/client/ClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ export abstract class ClientApplication {
authority: discoveredAuthority,
clientCapabilities: this.config.auth.clientCapabilities
},
systemOptions: {
proxyUrl: this.config.system.proxyUrl,
},
loggerOptions: {
logLevel: this.config.system.loggerOptions.logLevel,
loggerCallback: this.config.system.loggerOptions
Expand Down Expand Up @@ -387,6 +390,6 @@ export abstract class ClientApplication {
authorityMetadata: this.config.auth.authorityMetadata,
azureRegionConfiguration
};
return await AuthorityFactory.createDiscoveredInstance(authorityString, this.config.system.networkClient, this.storage, authorityOptions);
return await AuthorityFactory.createDiscoveredInstance(authorityString, this.config.system.networkClient, this.storage, authorityOptions, this.config.system.proxyUrl);
}
}
2 changes: 2 additions & 0 deletions lib/msal-node/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type CacheOptions = {
export type NodeSystemOptions = {
loggerOptions?: LoggerOptions;
networkClient?: INetworkModule;
proxyUrl?: string;
};

/**
Expand Down Expand Up @@ -105,6 +106,7 @@ const DEFAULT_LOGGER_OPTIONS: LoggerOptions = {
const DEFAULT_SYSTEM_OPTIONS: Required<NodeSystemOptions> = {
loggerOptions: DEFAULT_LOGGER_OPTIONS,
networkClient: NetworkUtils.getNetworkClient(),
proxyUrl: "",
};

export type NodeConfiguration = {
Expand Down