Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…tion-library-for-js into hydrate-cache-api
  • Loading branch information
tnorling committed Jul 31, 2023
2 parents 986c7c1 + d8b4b70 commit 424fbf9
Show file tree
Hide file tree
Showing 28 changed files with 942 additions and 145 deletions.
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 @@ -664,7 +664,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 @@ -577,6 +592,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 424fbf9

Please sign in to comment.