Skip to content

Commit

Permalink
Merge branch 'dev' into oidc-known-authorities
Browse files Browse the repository at this point in the history
  • Loading branch information
shylasummers authored Aug 1, 2023
2 parents c831fff + e698e4e commit ce1bacd
Show file tree
Hide file tree
Showing 29 changed files with 947 additions and 150 deletions.
8 changes: 4 additions & 4 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

# MSAL Angular
/lib/msal-angular/ @jo-arroyo @tnorling @peterzenz
/samples/msal-angular-v3-samples/ @derisen @jo-arroyo @tnorling @peterzenz
/samples/msal-angular-v3-samples/ @jo-arroyo @tnorling @peterzenz

# MSAL Browser
/lib/msal-browser/ @sameerag @tnorling @hectormmg @jo-arroyo @peterzenz @konstantin-msft
/samples/msal-browser-samples/ @derisen @sameerag @tnorling @hectormmg @jo-arroyo @peterzenz @konstantin-msft
/samples/msal-browser-samples/ @sameerag @tnorling @hectormmg @jo-arroyo @peterzenz @konstantin-msft

# MSAL Common
/lib/msal-common/ @sameerag @tnorling @hectormmg @jo-arroyo @peterzenz @robbie-microsoft @konstantin-msft

# MSAL Node
/lib/msal-node/ @sameerag @tnorling @hectormmg @peterzenz @jennyf19 @jmprieur @bgavrilMS @robbie-microsoft
/samples/msal-node-samples/ @derisen @sameerag @tnorling @hectormmg @peterzenz @bgavrilMS @robbie-microsoft
/samples/msal-node-samples/ @sameerag @tnorling @hectormmg @peterzenz @bgavrilMS @robbie-microsoft
/extensions/msal-node-extensions/ @sameerag @tnorling @hectormmg @peterzenz

# MSAL React
/lib/msal-react/ @tnorling @jo-arroyo @peterzenz
/samples/msal-react-samples/ @derisen @tnorling @jo-arroyo @peterzenz
/samples/msal-react-samples/ @tnorling @jo-arroyo @peterzenz

# Build
/build/ @sameerag @tnorling @hectormmg @peterzenz
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add storeInCache request parameter to control which tokens are persisted to the cache",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add storeInCache request parameter to control which tokens are persisted to the cache",
"packageName": "@azure/msal-common",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Omit new storeInCache request parameter from public API surface",
"packageName": "@azure/msal-node",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion lib/msal-browser/docs/device-bound-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ A working sample can be found [here](https://github.com/AzureAD/microsoft-authen

There are a few things that may behave a little differently when acquiring tokens through WAM.

- The `forceRefresh` parameter for `acquireTokenSilent` calls is not supported by WAM. You may receive a cached token from WAM regardless of what this flag is set to.
- All cache related configuration applies only to MSAL's local cache. The native broker controls its own, more secure, cache which is used instead of browser storage and it does not support configuration of its cache behavior. This means you may receive a cached token regardless of the value of request parameters such as: `forceRefresh`, `cacheLookupPolicy` or `storeInCache`. In addition, tokens received from the native broker are _not_ stored in local or session storage regardless of what you have configured on PublicClientApplication.
- If WAM needs to prompt the user for interaction a system prompt will be opened. This prompt looks a bit different from the browser popup windows you may be used to.
- Switching your account in the WAM prompt is not supported and MSAL.js will throw an error (Error Code: user_switch) if this happens. It is your app's responsibility to catch this error and handle it in a way that makes sense for your scenarios (e.g. Show an error page, retry with the new account, retry with the original account, etc.)
3 changes: 2 additions & 1 deletion lib/msal-browser/src/broker/nativeBroker/NativeRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { NativeExtensionMethod } from "../../utils/BrowserConstants";
import { StringDict } from "@azure/msal-common";
import { StoreInCache, StringDict } from "@azure/msal-common";

/**
* Token request which native broker will use to acquire tokens
Expand All @@ -30,6 +30,7 @@ export type NativeTokenRequest = {
resourceRequestUri?: string;
extendedExpiryToken?: boolean;
extraParameters?: StringDict;
storeInCache?: StoreInCache; // Object of booleans indicating whether to store tokens in the cache or not (default is true)
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,10 @@ export class NativeInteractionClient extends BaseInteractionClient {
cachedAccessToken
);

this.nativeStorageManager.saveCacheRecord(nativeCacheRecord);
this.nativeStorageManager.saveCacheRecord(
nativeCacheRecord,
request.storeInCache
);
}

protected addTelemetryFromNativeResponse(
Expand Down
183 changes: 147 additions & 36 deletions lib/msal-browser/test/interaction_client/NativeInteractionClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { SilentCacheClient } from "../../src/interaction_client/SilentCacheClien
import { NativeExtensionRequestBody } from "../../src/broker/nativeBroker/NativeRequest";
import { getDefaultPerformanceClient } from "../utils/TelemetryUtils";
import { CryptoOps } from "../../src/crypto/CryptoOps";
import { BrowserCacheManager } from "../../src/cache/BrowserCacheManager";

const networkInterface = {
sendGetRequestAsync<T>(): T {
Expand Down Expand Up @@ -94,53 +95,67 @@ const testCacheRecord: CacheRecord = {
describe("NativeInteractionClient Tests", () => {
globalThis.MessageChannel = require("worker_threads").MessageChannel; // jsdom does not include an implementation for MessageChannel

let pca = new PublicClientApplication({
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
},
});
let pca: PublicClientApplication;
let nativeInteractionClient: NativeInteractionClient;

let browserCacheManager: BrowserCacheManager;
let internalStorage: BrowserCacheManager;

//Implementation of PCA was moved to controller.
pca = (pca as any).controller;

const wamProvider = new NativeMessageHandler(
pca.getLogger(),
2000,
getDefaultPerformanceClient(),
new CryptoOps(new Logger({}))
);
// @ts-ignore
const nativeInteractionClient = new NativeInteractionClient(
// @ts-ignore
pca.config,
// @ts-ignore
pca.browserStorage,
// @ts-ignore
pca.browserCrypto,
pca.getLogger(),
// @ts-ignore
pca.eventHandler,
// @ts-ignore
pca.navigationClient,
ApiId.acquireTokenRedirect,
// @ts-ignore
pca.performanceClient,
wamProvider,
"nativeAccountId",
// @ts-ignore
pca.nativeInternalStorage,
RANDOM_TEST_GUID
);
let wamProvider: NativeMessageHandler;
let postMessageSpy: sinon.SinonSpy;
let mcPort: MessagePort;

beforeEach(() => {
pca = new PublicClientApplication({
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
},
});

//Implementation of PCA was moved to controller.
pca = (pca as any).controller;

//@ts-ignore
browserCacheManager = pca.browserStorage;
//@ts-ignore
internalStorage = pca.nativeInternalStorage;

wamProvider = new NativeMessageHandler(
pca.getLogger(),
2000,
getDefaultPerformanceClient(),
new CryptoOps(new Logger({}))
);

nativeInteractionClient = new NativeInteractionClient(
// @ts-ignore
pca.config,
// @ts-ignore
pca.browserStorage,
// @ts-ignore
pca.browserCrypto,
pca.getLogger(),
// @ts-ignore
pca.eventHandler,
// @ts-ignore
pca.navigationClient,
ApiId.acquireTokenRedirect,
// @ts-ignore
pca.performanceClient,
wamProvider,
"nativeAccountId",
// @ts-ignore
pca.nativeInternalStorage,
RANDOM_TEST_GUID
);

postMessageSpy = sinon.spy(window, "postMessage");
sinon.stub(MessageEvent.prototype, "source").get(() => window); // source property not set by jsdom window messaging APIs
});

afterEach(() => {
mcPort && mcPort.close();
jest.restoreAllMocks();
sinon.restore();
sessionStorage.clear();
localStorage.clear();
Expand Down Expand Up @@ -571,6 +586,102 @@ describe("NativeInteractionClient Tests", () => {
expect(response.account).toEqual(testAccount);
expect(response.tokenType).toEqual(AuthenticationScheme.BEARER);
});

describe("storeInCache tests", () => {
const mockWamResponse = {
access_token: TEST_TOKENS.ACCESS_TOKEN,
id_token: TEST_TOKENS.IDTOKEN_V2,
scope: "User.Read",
expires_in: 3600,
client_info: TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO,
account: {
id: "nativeAccountId",
},
properties: {},
};

beforeEach(() => {
jest.spyOn(
NativeMessageHandler.prototype,
"sendMessage"
).mockResolvedValue(mockWamResponse);
});

it("does not store idToken if storeInCache.idToken = false", async () => {
const response = await nativeInteractionClient.acquireToken({
scopes: ["User.Read"],
storeInCache: {
idToken: false,
},
});
expect(response.accessToken).toEqual(
mockWamResponse.access_token
);
expect(response.idToken).toEqual(mockWamResponse.id_token);

// Browser Storage should not contain tokens
const tokenKeys = browserCacheManager.getTokenKeys();
expect(tokenKeys.idToken).toHaveLength(0);
expect(tokenKeys.accessToken).toHaveLength(0);
expect(tokenKeys.refreshToken).toHaveLength(0);

// Cache should not contain tokens which were turned off
const internalTokenKeys = internalStorage.getTokenKeys();
expect(internalTokenKeys.idToken).toHaveLength(0);
expect(internalTokenKeys.accessToken).toHaveLength(1);
expect(internalTokenKeys.refreshToken).toHaveLength(0); // RT will never be returned by WAM
});

it("does not store accessToken if storeInCache.accessToken = false", async () => {
const response = await nativeInteractionClient.acquireToken({
scopes: ["User.Read"],
storeInCache: {
accessToken: false,
},
});
expect(response.accessToken).toEqual(
mockWamResponse.access_token
);
expect(response.idToken).toEqual(mockWamResponse.id_token);

// Cache should not contain tokens which were turned off
const tokenKeys = browserCacheManager.getTokenKeys();
expect(tokenKeys.idToken).toHaveLength(0);
expect(tokenKeys.accessToken).toHaveLength(0);
expect(tokenKeys.refreshToken).toHaveLength(0);

// Cache should not contain tokens which were turned off
const internalTokenKeys = internalStorage.getTokenKeys();
expect(internalTokenKeys.idToken).toHaveLength(1);
expect(internalTokenKeys.accessToken).toHaveLength(0);
expect(internalTokenKeys.refreshToken).toHaveLength(0); // RT will never be returned by WAM
});

it("does not store refreshToken if storeInCache.refreshToken = false", async () => {
const response = await nativeInteractionClient.acquireToken({
scopes: ["User.Read"],
storeInCache: {
refreshToken: false,
},
});
expect(response.accessToken).toEqual(
mockWamResponse.access_token
);
expect(response.idToken).toEqual(mockWamResponse.id_token);

// Browser Storage should not contain tokens
const tokenKeys = browserCacheManager.getTokenKeys();
expect(tokenKeys.idToken).toHaveLength(0);
expect(tokenKeys.accessToken).toHaveLength(0);
expect(tokenKeys.refreshToken).toHaveLength(0);

// Cache should not contain tokens which were turned off
const internalTokenKeys = internalStorage.getTokenKeys();
expect(internalTokenKeys.idToken).toHaveLength(1);
expect(internalTokenKeys.accessToken).toHaveLength(1);
expect(internalTokenKeys.refreshToken).toHaveLength(0); // RT will never be returned by WAM
});
});
});

describe("acquireTokenRedirect tests", () => {
Expand Down
Loading

0 comments on commit ce1bacd

Please sign in to comment.