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] hone surface for msal extension library #1687

Closed
wants to merge 10 commits into from

Conversation

DarylThayil
Copy link
Contributor

@DarylThayil DarylThayil commented May 21, 2020

Plan

Begin with developer called cache apis

import { PublicClientApplication } from "msal-node"
import { PersistenceCachePlugin } from "msal-extension"


const publicApplication = new PublicClientApplication({
    auth: {...},
    cache: {
        cachePlugin: PersistenceCachePlugin
    }
});
const cacheManager = publicApplication.getCacheManager();

await cacheManager.deserialize(); // read into memmory

// do a bunch of stuff that results in memmory cache changes

await cacheManager.serialize(); //write to persistence

Eventually we will instrument public apis / storage with these calls but for the sake of end to end, and still enabling customers who may not want cache calls everytime they call an api, this should be a good starting point.

Msal Extension will expose two functions that provideWithPersistence will provide

export interface ICachePlugin {
    readFromStorage: () => Promise<string>;
    writeToStorage: (getMergedState: (oldState: string) => string) => Promise<void>;
}

readFromStorage will implement reading the persistent cache file and providing it as a return to the lib
writetoStorage will do fun stuff, accepting a callback called getMergedState(stateFromDisk) which will return a merged state of in memmory and disk. This mainly entails makingsure removals are respsected and updates and new additions happen.
When the function is envoked a file lock will begin, a read will happen and pass to getMergedState, which will pass back the state to be written, and then the lock will be lifted.

Msal Node

  • readFromPersistence / setCache should over write internal memmory
  • writeToPersistance should Lock -> read persistance file -> merge updates and removals -> write file -> unlock
  • merge logic lives at this layer so that anyone implementing persistence does not need to worry about this logic.

Extension

Implents readFromStorage: () => Promise;, writeToStorage: (getMergedState: (oldState: string) => string) => Promise;

@jasonnutter
Copy link
Contributor

Your pseudo code above doesn't align with teh sample code. Are we providing the implementation to MSAL Node and then calling an API on MSAL, or are we providing MSAL Node to the extension library and then calling an API on the extension library?

I would personally advocate strongly for providing the implementation to MSAL Node and calling an API on MSAL node (which invokes the provided implementations from the extension library).

Copy link
Contributor

@sangonzal sangonzal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. This still seems essentially different than any of the other libraries cache models in that it requires users to wrap their MSAL code in deserialize and serialize calls, which is not nearly as customer friendly as it requires them understanding what MSAL operations might alter the cache. There's no way of working around that without making Storage async first though.

@DarylThayil DarylThayil changed the title hone surface for msal extension library [msal-node] hone surface for msal extension library May 21, 2020
@DarylThayil
Copy link
Contributor Author

@jasonnutter originally I thought about doing the passing extension to node model, but after chatting with @sangonzal , It sort of became clear that the write to Storage and readFrom storage were somewhat duplicative of the get cache and set cache functions. Cache Manager is an attempt to say, there is one way to get cache state from the lib (Cache Manager), so if you hand roll your own persistence, you can use that. If you use our persistence model, just use the provider from it so that CacheManager apis write and read from it. Seems like a reasonable way to keep the surface low, but the extensibility high.

it also lets us stay away from altering storage to async

@DarylThayil
Copy link
Contributor Author

@sangonzal agree that the wrapping is a bit unfortunate, but in async await code it looks much cleaner

await cacheManager.deserialize();
// do stuff
await cacheManager.serialize(); 

@sangonzal
Copy link
Contributor

sangonzal commented May 21, 2020

@sangonzal agree that the wrapping is a bit unfortunate, but in async await code it looks much cleaner

await cacheManager.deserialize();
// do stuff
await cacheManager.serialize(); 

@DarylThayil Understood, my concern is that it leaks out some details that customers don't necessarily have to be aware of. With this model, callers have to always wrap msal code in deserialize() and serialize().

This brings the question, if we are asking people to always do this, why can't we just do this in MSAL and hide it from callers? Essentially the only thing that's keeping us from incorporating this into MSAL is keeping Storage synchronous.

All to say, we are making a tradeoff here that we should be aware of- Keeping storage synchronous (because we want to share cache code with msal- browser) at the expense of a more complicated Node API.

@DarylThayil
Copy link
Contributor Author

@sangonzal and I spent a ton of time detailing what needs to happen and think we have a good understanding

lib/msal-node/src/cache/CacheManager.ts Outdated Show resolved Hide resolved
lib/msal-node/src/cache/CacheManager.ts Outdated Show resolved Hide resolved
lib/msal-node/src/cache/CacheManager.ts Outdated Show resolved Hide resolved
samples/msal-node-auth-code/index.js Outdated Show resolved Hide resolved
samples/msal-node-auth-code/index.js Outdated Show resolved Hide resolved
lib/msal-node/src/cache/Storage.ts Show resolved Hide resolved
lib/msal-node/src/cache/Storage.ts Outdated Show resolved Hide resolved
} else {
throw Error("Cache Must be passed in or configured");
}
this.storage.setCache(deserializedCache)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit pick: not a big fan of mutation here, as it doesn't really add much value.

if (this.persistence) {
  const stringCacheFromStorage = await this.persistence.readFromStorage();
  const deserlizedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(stringCacheFromStorage)));
  this.storage.setCache(deserializedCache);
  return;
}

if (cache) {
  const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache)));
  this.storage.setCache(deserializedCache);
  return;
}

throw Error("Cache Must be passed in or configured");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolving

lib/msal-node/src/cache/CacheManager.ts Outdated Show resolved Hide resolved
lib/msal-node/src/cache/CacheManager.ts Outdated Show resolved Hide resolved
@DarylThayil DarylThayil force-pushed the node-cache-surface branch 2 times, most recently from a881caa to 085ab58 Compare June 2, 2020 16:46
Copy link
Contributor

@sangonzal sangonzal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. As you documented as part of this PR, we should consider separating serialize/deserialize from from persistence storage - that can be done in a follow up.

lib/msal-node/src/config/Configuration.ts Show resolved Hide resolved
@github-actions github-actions bot added msal-angular Related to @azure/msal-angular package msal-browser Related to msal-browser package msal-common Related to msal-common package labels Jun 4, 2020
@sameerag sameerag changed the base branch from msal-node-cache-fileops-async to dev June 4, 2020 20:37
Copy link
Member

@sameerag sameerag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. Please merge only after @sangonzal adds merge code.

@coveralls
Copy link

coveralls commented Jun 4, 2020

Coverage Status

Coverage decreased (-0.01%) to 76.613% when pulling 7c4b27a on node-cache-surface into 504d352 on dev.

@github-actions github-actions bot added msal-node Related to msal-node package samples Related to the samples apps for the library. labels Jun 16, 2020
@@ -18,6 +18,7 @@ export type RefreshTokenCache = { [key: string]: RefreshTokenEntity };
export type AppMetadataCache = { [key: string]: AppMetadataEntity };

export type JsonCache = {
[key: string]: StringDict;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this? Is this allowing arbitrary nested StrictDict object?

}
});

return oldState;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of mutating the passed in parameter (which lint rules will not allow in the future), can you construct a new object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will update.

})
}
});
return oldState;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about mutating passed in value.

@sangonzal sangonzal mentioned this pull request Jun 19, 2020
@sameerag
Copy link
Member

Closing as this merged with #1801

@sameerag sameerag closed this Jun 24, 2020
@sangonzal sangonzal deleted the node-cache-surface branch July 17, 2020 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
msal-angular Related to @azure/msal-angular package msal-browser Related to msal-browser package msal-common Related to msal-common package msal-node Related to msal-node package samples Related to the samples apps for the library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants