Skip to content

Commit

Permalink
Merge branch 'dev' into update_cinm_sample
Browse files Browse the repository at this point in the history
  • Loading branch information
rgins16 committed Dec 7, 2022
2 parents 3af18eb + dc9426a commit 9829e5e
Show file tree
Hide file tree
Showing 15 changed files with 7,460 additions and 19,917 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Added caching methods to set or get redirect context of application during redirect flow. #5411",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Improvement to Cloud Instance Discovery #5448",
"packageName": "@azure/msal-common",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Update react dev dependency",
"packageName": "@azure/msal-react",
"email": "[email protected]",
"dependentChangeType": "none"
}
10 changes: 10 additions & 0 deletions lib/msal-browser/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
**[Compatibility](#Compatibility)**

1. [What browsers are supported by MSAL.js?](#what-browsers-are-supported-by-msaljs)
1. [Does MSAL.js support mobile browsers?](#does-msal-support-mobile-browsers)
1. [I am moving from MSAL.js 1.x to MSAL.js to 2.x. What should I know?](#i-am-moving-from-msaljs-1x-to-msaljs-to-2x-what-should-i-know)
1. [Does this library work for iframed applications?](#does-this-library-work-for-iframed-applications)
1. [Will MSAL 2.x support B2C?](#will-msal-2x-support-b2c)
Expand Down Expand Up @@ -102,6 +103,15 @@ There are certain known issues and mitigations documented for the following brow
- [Browsers that block 3rd Party Cookies (i.e. Safari, Chrome Incognito, Firefox Private)](https://docs.microsoft.com/azure/active-directory/develop/reference-third-party-cookies-spas)
- [IE 11 and Edge Legacy](./docs/internet-explorer.md)

## Does MSAL support mobile browsers?

In general, MSAL.js does support mobile browsers. However, since MSAL.js isn't tested specifically against mobile browsers, support may be limited and some features and flows may not work.

For example, while MSAL's `loginPopup` and `acquireTokenPopup` APIs may work on some mobile browsers, using popup APIs is discouraged on mobile devices as not all browsers will support this flow. We recommend using redirect APIs such as `loginRedirect` and `acquireTokenRedirect` when your application is running on a mobile device.

For problems specific to mobile browsers not listed here, please open an issue with clear instructions to reproduce.


## I am moving from MSAL.js 1.x to MSAL.js to 2.x. What should I know?

Please refer to our migration guide [here](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/v1-migration.md).
Expand Down
15 changes: 15 additions & 0 deletions lib/msal-browser/src/cache/BrowserCacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,21 @@ export class BrowserCacheManager extends CacheManager {

return currentCacheKey;
}

/**
* Returns application id as redirect context during AcquireTokenRedirect flow.
*/
getRedirectRequestContext(): string | null {
return this.getTemporaryCache(TemporaryCacheKeys.REDIRECT_CONTEXT, true);
}

/**
* Sets application id as the redirect context during AcquireTokenRedirect flow.
* @param value
*/
setRedirectRequestContext(value: string): void {
this.setTemporaryCache(TemporaryCacheKeys.REDIRECT_CONTEXT, value, true);
}
}

export const DEFAULT_BROWSER_CACHE_MANAGER = (clientId: string, logger: Logger): BrowserCacheManager => {
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-browser/src/utils/BrowserConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ export enum TemporaryCacheKeys {
INTERACTION_STATUS_KEY = "interaction.status",
CCS_CREDENTIAL = "ccs.credential",
CORRELATION_ID = "request.correlationId",
NATIVE_REQUEST = "request.native"
NATIVE_REQUEST = "request.native",
REDIRECT_CONTEXT = "request.redirect.context"
}

/**
Expand Down
13 changes: 13 additions & 0 deletions lib/msal-browser/test/cache/BrowserCacheManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,19 @@ describe("BrowserCacheManager tests", () => {
expect(browserLocalStorage.getThrottlingCache(testKey)).toBeInstanceOf(ThrottlingEntity);
});
});

describe("RedirectRequestContext", () => {
it("Returns redirect request context as null if context not set in browser cache", () => {
expect(browserSessionStorage.getRedirectRequestContext()).toEqual(null);
});

it("Returns redirect request context if context set in browser cache", () => {
const testVal = "testId";
browserSessionStorage.setRedirectRequestContext(testVal);
expect(browserSessionStorage.getRedirectRequestContext()).toEqual(testVal);
});

});
});
});

Expand Down
51 changes: 42 additions & 9 deletions lib/msal-common/src/authority/Authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import { ICacheManager } from "../cache/interface/ICacheManager";
import { AuthorityMetadataEntity } from "../cache/entities/AuthorityMetadataEntity";
import { AuthorityOptions , AzureCloudInstance } from "./AuthorityOptions";
import { CloudInstanceDiscoveryResponse, isCloudInstanceDiscoveryResponse } from "./CloudInstanceDiscoveryResponse";
import { CloudInstanceDiscoveryErrorResponse, isCloudInstanceDiscoveryErrorResponse } from "./CloudInstanceDiscoveryErrorResponse";
import { CloudDiscoveryMetadata } from "./CloudDiscoveryMetadata";
import { RegionDiscovery } from "./RegionDiscovery";
import { RegionDiscoveryMetadata } from "./RegionDiscoveryMetadata";
import { ImdsOptions } from "./ImdsOptions";
import { AzureCloudOptions } from "../config/ClientConfiguration";
import { Logger } from "../logger/Logger";
import { AuthError } from "../error/AuthError";

/**
* The authority class validates the authority URIs used by the user, and retrieves the OpenID Configuration Data from the
Expand Down Expand Up @@ -468,7 +470,7 @@ export class Authority {
this.logger.verbose("Did not find cloud discovery metadata in the cache... Attempting to get cloud discovery metadata from the network.");
metadata = await this.getCloudDiscoveryMetadataFromNetwork();
if (metadata) {
this.logger.verbose("Found cloud discovery metadata from the network.");
this.logger.verbose("cloud discovery metadata was successfully returned from getCloudDiscoveryMetadataFromNetwork()");
metadataEntity.updateCloudDiscoveryMetadata(metadata, true);
return AuthorityMetadataSource.NETWORK;
}
Expand Down Expand Up @@ -543,27 +545,58 @@ export class Authority {
let match = null;
try {
const response =
await this.networkInterface.sendGetRequestAsync<CloudInstanceDiscoveryResponse>(
await this.networkInterface.sendGetRequestAsync<CloudInstanceDiscoveryResponse | CloudInstanceDiscoveryErrorResponse>(
instanceDiscoveryEndpoint,
options
);
const metadata = isCloudInstanceDiscoveryResponse(response.body)
? response.body.metadata
: [];
if (metadata.length === 0) {
// If no metadata is returned, authority is untrusted

let typedResponseBody: CloudInstanceDiscoveryResponse | CloudInstanceDiscoveryErrorResponse;
let metadata: Array<CloudDiscoveryMetadata>;
if (isCloudInstanceDiscoveryResponse(response.body)) {
typedResponseBody = response.body as CloudInstanceDiscoveryResponse;
metadata = typedResponseBody.metadata;

this.logger.verbosePii(`tenant_discovery_endpoint is: ${typedResponseBody.tenant_discovery_endpoint}`);
} else if (isCloudInstanceDiscoveryErrorResponse(response.body)) {
this.logger.warning(`A CloudInstanceDiscoveryErrorResponse was returned. The cloud instance discovery network request's status code is: ${response.status}`);

typedResponseBody = response.body as CloudInstanceDiscoveryErrorResponse;
if (typedResponseBody.error === Constants.INVALID_INSTANCE) {
this.logger.error("The CloudInstanceDiscoveryErrorResponse error is invalid_instance.");
return null;
}

this.logger.warning(`The CloudInstanceDiscoveryErrorResponse error is ${typedResponseBody.error}`);
this.logger.warning(`The CloudInstanceDiscoveryErrorResponse error description is ${typedResponseBody.error_description}`);

this.logger.warning("Setting the value of the CloudInstanceDiscoveryMetadata (returned from the network) to []");
metadata = [];
} else {
this.logger.error("AAD did not return a CloudInstanceDiscoveryResponse or CloudInstanceDiscoveryErrorResponse");
return null;
}

this.logger.verbose("Attempting to find a match between the developer's authority and the CloudInstanceDiscoveryMetadata returned from the network request.");
match = Authority.getCloudDiscoveryMetadataFromNetworkResponse(
metadata,
this.hostnameAndPort
);
} catch (e) {
} catch (error) {
if (error instanceof AuthError) {
this.logger.error(`There was a network error while attempting to get the cloud discovery instance metadata.\nError: ${error.errorCode}\nError Description: ${error.errorMessage}`);
} else {
const typedError = error as Error;
this.logger.error(`A non-MSALJS error was thrown while attempting to get the cloud instance discovery metadata.\nError: ${typedError.name}\nError Description: ${typedError.message}`);
}

return null;
}

// Custom Domain scenario, host is trusted because Instance Discovery call succeeded
if (!match) {
// Custom Domain scenario, host is trusted because Instance Discovery call succeeded
this.logger.warning("The developer's authority was not found within the CloudInstanceDiscoveryMetadata returned from the network request.");
this.logger.verbose("Creating custom Authority for custom domain scenario.");

match = Authority.createCloudDiscoveryMetadataFromHost(
this.hostnameAndPort
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

/**
* The OpenID Configuration Endpoint Response type. Used by the authority class to get relevant OAuth endpoints.
*/
export type CloudInstanceDiscoveryErrorResponse = {
error: String;
error_description: String;
error_codes?: Array<Number>;
timestamp?: String;
trace_id?: String;
correlation_id?: String;
error_uri?: String;
};

export function isCloudInstanceDiscoveryErrorResponse(response: object): boolean {
return (
response.hasOwnProperty("error") &&
response.hasOwnProperty("error_description")
);
}
3 changes: 2 additions & 1 deletion lib/msal-common/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export const Constants = {
KNOWN_PUBLIC_CLOUDS: ["login.microsoftonline.com", "login.windows.net", "login.microsoft.com", "sts.windows.net"],
TOKEN_RESPONSE_TYPE: "token",
ID_TOKEN_RESPONSE_TYPE: "id_token",
SHR_NONCE_VALIDITY: 240
SHR_NONCE_VALIDITY: 240,
INVALID_INSTANCE: "invalid_instance",
};

export const OIDC_DEFAULT_SCOPES = [
Expand Down
27 changes: 25 additions & 2 deletions lib/msal-common/test/authority/Authority.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ describe("Authority.ts Class Unit Tests", () => {
});
});

it("throws untrustedAuthority error if host is not part of knownAuthorities, cloudDiscoveryMetadata and instance discovery network call doesn't return metadata", (done) => {
it("throws untrustedAuthority error if host is not part of knownAuthorities, cloudDiscoveryMetadata and instance discovery network call doesn't return metadata, and the error returned from AAD is 'invalid_instance'", (done) => {
const authorityOptions: AuthorityOptions = {
protocolMode: ProtocolMode.AAD,
knownAuthorities: [],
Expand All @@ -922,7 +922,8 @@ describe("Authority.ts Class Unit Tests", () => {
networkInterface.sendGetRequestAsync = (url: string, options?: NetworkRequestOptions): any => {
return {
body: {
error: "This endpoint does not exist"
error: Constants.INVALID_INSTANCE,
error_description: "error_description"
}
};
};
Expand All @@ -936,6 +937,28 @@ describe("Authority.ts Class Unit Tests", () => {
});
});

it("throws untrustedAuthority error if host is not part of knownAuthorities, cloudDiscoveryMetadata and instance discovery network call doesn't return metadata, and the error returned from AAD is NOT 'invalid_instance'", async () => {
const authorityOptions: AuthorityOptions = {
protocolMode: ProtocolMode.AAD,
knownAuthorities: [],
cloudDiscoveryMetadata: "",
authorityMetadata: ""
}
networkInterface.sendGetRequestAsync = (url: string, options?: NetworkRequestOptions): any => {
return {
body: {
error: "not_invalid_instance",
error_description: "error_description"
}
};
};
jest.spyOn(Authority.prototype, <any>"updateEndpointMetadata").mockResolvedValue("cache");
authority = new Authority(Constants.DEFAULT_AUTHORITY, networkInterface, mockStorage, authorityOptions, logger);

await authority.resolveEndpointsAsync();
expect(authority.isAlias("login.microsoftonline.com")).toBe(true);
});

it("getPreferredCache throws error if discovery is not complete", () => {
expect(() => authority.getPreferredCache()).toThrowError(ClientAuthErrorMessage.endpointResolutionError.desc);
});
Expand Down
Loading

0 comments on commit 9829e5e

Please sign in to comment.