Skip to content

[msal-node] Cache-3: Add NodeStorage, token generation and separate SPAresponse #1519

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

Merged
merged 29 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f0255ee
Initial changes
sameerag Apr 9, 2020
0512a01
Merge branch 'msal-node-cache-utils' into msal-node-cache-generator
sameerag Apr 9, 2020
1124a56
Merge branch 'msal-node-cache-utils' into msal-node-cache-generator
sameerag Apr 13, 2020
8633ceb
add client_info in response
sameerag Apr 13, 2020
2dd9672
Adding ResponseHandler, AuthenticationResult generation and Credentia…
sameerag Apr 13, 2020
00d639e
All changes - will create separate PRs later
sameerag Apr 16, 2020
b974367
End-to-End cache read/write working
sameerag Apr 17, 2020
461a8c5
Merge branch 'msal-node-cache-utils' into msal-node-cache-generator
sameerag Apr 17, 2020
3371ff5
More changes for the demo
sameerag Apr 19, 2020
516d004
Cache PR - Add Storage, token generation and rename response
sameerag Apr 20, 2020
185be7c
Response Initial changes
sameerag Apr 20, 2020
4f98589
Update node with result type
sameerag Apr 20, 2020
bf00ed5
Final changes for cache end-to-end Phase-I
sameerag Apr 20, 2020
29b4210
Update CacheTypes include
sameerag Apr 20, 2020
62ba010
Merge branch 'msal-node-cache-utils' into msal-node-response-cacheTokens
sameerag May 1, 2020
4e32965
Addressing feedback
sameerag May 1, 2020
53d2200
Merge branch 'msal-node-response-cacheTokens' into msal-node-response
sameerag May 1, 2020
8f93841
addressing comments
sameerag May 1, 2020
75877aa
Merge branch 'msal-node-response' into msal-node-cache-final
sameerag May 1, 2020
e4c1316
Update Storage.ts
sameerag May 1, 2020
49908e6
Merge branch 'msal-node-response' into msal-node-cache-final
sameerag May 1, 2020
63f4e44
Fixing compiler issues
sameerag May 1, 2020
a459a2f
Merge branch 'msal-node-cache-utils' into msal-node-response-cacheTokens
sameerag May 6, 2020
df671bb
Fixing compiler issues
sameerag May 6, 2020
cbf1105
Merge branch 'msal-node-response-cacheTokens' into msal-node-response
sameerag May 6, 2020
cfa4b6d
Merge branch 'msal-node-response' into msal-node-cache-final
sameerag May 6, 2020
36ac337
remove duplicate declaration in merge
sameerag May 6, 2020
4c77d1c
Merge pull request #1522 from AzureAD/msal-node-cache-final
sameerag May 6, 2020
5e4d601
Merge pull request #1520 from AzureAD/msal-node-response
sameerag May 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions lib/msal-common/src/cache/CacheHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class CacheHelpers {

constructor(cacheImpl: ICacheStorage) {
this.cacheStorage = cacheImpl;

}

/**
Expand Down Expand Up @@ -167,11 +168,11 @@ export class CacheHelpers {

/**
* Checks that any parameters are exact matches for key value, since key.match in the above functions only do contains checks, not exact matches.
* @param atKey
* @param clientId
* @param authority
* @param resource
* @param homeAccountIdentifier
* @param atKey
* @param clientId
* @param authority
* @param resource
* @param homeAccountIdentifier
*/
private checkForExactKeyMatch(atKey: AccessTokenKey, clientId: string, authority: string, resource?: string, homeAccountIdentifier?: string): boolean {
const hasClientId = (atKey.clientId === clientId);
Expand Down
28 changes: 23 additions & 5 deletions lib/msal-common/src/cache/ICacheStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,49 @@
* Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens.
*/
export interface ICacheStorage {
/**
* Function to read serialized Cache from disk
* @param key
* @param value
*/
getSerializedCache(): Promise<string>;

/**
* Function to write serialized Cache to disk
* @param cache
*/
setSerializedCache(cache: string): void;

/**
* Function to set item in cache.
* @param key
* @param value
* @param key
* @param value
*/
setItem(key: string, value: string): void;

/**
* Function which retrieves item from cache.
* @param key
* @param key
*/
getItem(key: string): string;

/**
* Function which removes item from cache.
* @param key
* @param key
*/
removeItem(key: string): void;

/**
* Function which returns boolean whether cache contains a specific key.
* @param key
* @param key
*/
containsKey(key: string): boolean;

/**
* Function which retrieves all current keys from the cache.
*/
getKeys(): string[];

/**
* Function which clears cache.
*/
Expand Down
30 changes: 24 additions & 6 deletions lib/msal-common/src/client/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { RequestParameterBuilder } from "../server/RequestParameterBuilder";
import { RequestValidator } from "../request/RequestValidator";
import { GrantType } from "../utils/Constants";
import { ClientConfiguration } from "../config/ClientConfiguration";
import {ServerAuthorizationTokenResponse} from "../server/ServerAuthorizationTokenResponse";
import {NetworkResponse} from "../network/NetworkManager";
import {ScopeSet} from "../request/ScopeSet";
import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse";
import { NetworkResponse } from "../network/NetworkManager";
import { ScopeSet } from "../request/ScopeSet";
import { ResponseHandler } from "../response/ResponseHandler";
import { AuthenticationResult } from "../response/AuthenticationResult";

/**
* Oauth2.0 Authorization Code client
Expand Down Expand Up @@ -45,13 +47,29 @@ export class AuthorizationCodeClient extends BaseClient {
* API to acquire a token in exchange of 'authorization_code` acquired by the user in the first leg of the authorization_code_grant
* @param request
*/
async acquireToken(request: AuthorizationCodeRequest): Promise<string> {
async acquireToken(request: AuthorizationCodeRequest): Promise<AuthenticationResult> {

this.logger.info("in acquireToken call");

const authority: Authority = await this.createAuthority(request && request.authority);
const response = await this.executeTokenRequest(authority, request);
return JSON.stringify(response.body);
// TODO add response_handler here to send the response

const responseHandler = new ResponseHandler(
this.config.authOptions.clientId,
this.unifiedCacheManager,
this.cryptoUtils,
this.logger
);

responseHandler.validateTokenResponse(response.body);
const tokenResponse = await responseHandler.generateAuthenticationResult(
response.body,
authority
);

// set the final cache and return the auth response
this.setCache();
return tokenResponse;
}

/**
Expand Down
29 changes: 23 additions & 6 deletions lib/msal-common/src/client/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { Account } from "../account/Account";
import { Authority } from "../authority/Authority";
import { Logger } from "../logger/Logger";
import { AuthorityFactory } from "../authority/AuthorityFactory";
import {AADServerParamKeys, Constants, HeaderNames} from "../utils/Constants";
import {ClientAuthError} from "../error/ClientAuthError";
import {NetworkResponse} from "../network/NetworkManager";
import {ServerAuthorizationTokenResponse} from "../server/ServerAuthorizationTokenResponse";
import { AADServerParamKeys, Constants, HeaderNames } from "../utils/Constants";
import { ClientAuthError } from "../error/ClientAuthError";
import { NetworkResponse } from "../network/NetworkManager";
import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse";
import { UnifiedCacheManager } from "../unifiedCache/UnifiedCacheManager";
import { Serializer } from "../unifiedCache/serialize/Serializer";

/**
* Base application class which will construct requests to send to and handle responses from the Microsoft STS using the authorization code flow.
Expand All @@ -37,7 +39,10 @@ export abstract class BaseClient {
protected networkClient: INetworkModule;

// Helper API object for running cache functions
protected cacheManager: CacheHelpers;
protected spaCacheManager: CacheHelpers;

// Helper API object for serialized cache operations
protected unifiedCacheManager: UnifiedCacheManager;

// Account object
protected account: Account;
Expand All @@ -59,7 +64,10 @@ export abstract class BaseClient {
this.cacheStorage = this.config.storageInterface;

// Initialize storage helper object
this.cacheManager = new CacheHelpers(this.cacheStorage);
this.spaCacheManager = new CacheHelpers(this.cacheStorage);

// Initialize serialized cache manager
this.unifiedCacheManager = new UnifiedCacheManager(this.cacheStorage);

// Set the network interface
this.networkClient = this.config.networkInterface;
Expand Down Expand Up @@ -131,4 +139,13 @@ export abstract class BaseClient {
headers: headers,
});
}

/**
* Set the cache post acquireToken call
*/
protected setCache(): void {
const inMemCache = this.unifiedCacheManager.getCacheInMemory();
const cache = this.unifiedCacheManager.generateJsonCache(inMemCache);
this.cacheStorage.setSerializedCache(Serializer.serializeJSONBlob(cache));
}
}
30 changes: 15 additions & 15 deletions lib/msal-common/src/client/SPAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ServerCodeRequestParameters } from "../server/ServerCodeRequestParamete
import { ServerTokenRequestParameters } from "../server/ServerTokenRequestParameters";
import { CodeResponse } from "../response/CodeResponse";
import { TokenResponse } from "../response/TokenResponse";
import { ResponseHandler } from "../response/ResponseHandler";
import { SPAResponseHandler } from "../response/SPAResponseHandler";
import { ServerAuthorizationCodeResponse } from "../server/ServerAuthorizationCodeResponse";
import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse";
import { ClientAuthError } from "../error/ClientAuthError";
Expand Down Expand Up @@ -101,7 +101,7 @@ export class SPAClient extends BaseClient {
}

// Update required cache entries for request.
this.cacheManager.updateCacheEntries(requestParameters, request.account);
this.spaCacheManager.updateCacheEntries(requestParameters, request.account);

// Populate query parameters (sid/login_hint/domain_hint) and any other extraQueryParameters set by the developer.
requestParameters.populateQueryParams(adalIdToken);
Expand All @@ -123,7 +123,7 @@ export class SPAClient extends BaseClient {
return urlNavigate;
} catch (e) {
// Reset cache items before re-throwing.
this.cacheManager.resetTempCacheItems(requestParameters && requestParameters.state);
this.spaCacheManager.resetTempCacheItems(requestParameters && requestParameters.state);
throw e;
}
}
Expand Down Expand Up @@ -171,7 +171,7 @@ export class SPAClient extends BaseClient {
return await this.getTokenResponse(tokenEndpoint, tokenReqParams, tokenRequest, codeResponse);
} catch (e) {
// Reset cache items and set account to null before re-throwing.
this.cacheManager.resetTempCacheItems(codeResponse && codeResponse.userRequestState);
this.spaCacheManager.resetTempCacheItems(codeResponse && codeResponse.userRequestState);
this.account = null;
throw e;
}
Expand Down Expand Up @@ -233,7 +233,7 @@ export class SPAClient extends BaseClient {

// Only populate id token if it exists in cache item.
return StringUtils.isEmpty(cachedTokenItem.value.idToken) ? defaultTokenResponse :
ResponseHandler.setResponseIdToken(defaultTokenResponse, new IdToken(cachedTokenItem.value.idToken, this.cryptoUtils));
SPAResponseHandler.setResponseIdToken(defaultTokenResponse, new IdToken(cachedTokenItem.value.idToken, this.cryptoUtils));
} else {
// Renew the tokens.
request.authority = cachedTokenItem.key.authority;
Expand All @@ -243,7 +243,7 @@ export class SPAClient extends BaseClient {
}
} catch (e) {
// Reset cache items and set account to null before re-throwing.
this.cacheManager.resetTempCacheItems();
this.spaCacheManager.resetTempCacheItems();
this.account = null;
throw e;
}
Expand All @@ -261,7 +261,7 @@ export class SPAClient extends BaseClient {
// Check for homeAccountIdentifier. Do not send anything if it doesn't exist.
const homeAccountIdentifier = currentAccount ? currentAccount.homeAccountIdentifier : "";
// Remove all pertinent access tokens.
this.cacheManager.removeAllAccessTokens(this.config.authOptions.clientId, authorityUri, "", homeAccountIdentifier);
this.spaCacheManager.removeAllAccessTokens(this.config.authOptions.clientId, authorityUri, "", homeAccountIdentifier);
// Clear remaining cache items.
this.cacheStorage.clear();
// Clear current account.
Expand Down Expand Up @@ -298,7 +298,7 @@ export class SPAClient extends BaseClient {
*/
public handleFragmentResponse(hashFragment: string): CodeResponse {
// Handle responses.
const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheStorage, this.cacheManager, this.cryptoUtils, this.logger);
const responseHandler = new SPAResponseHandler(this.config.authOptions.clientId, this.cacheStorage, this.spaCacheManager, this.cryptoUtils, this.logger);
// Deserialize hash fragment response parameters.
const hashUrlString = new UrlString(hashFragment);
const serverParams = hashUrlString.getDeserializedHash<ServerAuthorizationCodeResponse>();
Expand All @@ -315,7 +315,7 @@ export class SPAClient extends BaseClient {
*/
public cancelRequest(): void {
const cachedState = this.cacheStorage.getItem(TemporaryCacheKeys.REQUEST_STATE);
this.cacheManager.resetTempCacheItems(cachedState || "");
this.spaCacheManager.resetTempCacheItems(cachedState || "");
}

/**
Expand All @@ -329,7 +329,7 @@ export class SPAClient extends BaseClient {
this.cacheStorage.removeItem(TemporaryCacheKeys.REQUEST_PARAMS);
// Get cached authority and use if no authority is cached with request.
if (StringUtils.isEmpty(parsedRequest.authority)) {
const authorityKey: string = this.cacheManager.generateAuthorityKey(state);
const authorityKey: string = this.spaCacheManager.generateAuthorityKey(state);
const cachedAuthority: string = this.cacheStorage.getItem(authorityKey);
parsedRequest.authority = cachedAuthority;
}
Expand All @@ -348,7 +348,7 @@ export class SPAClient extends BaseClient {
*/
private getCachedTokens(requestScopes: ScopeSet, authorityUri: string, resourceId: string, homeAccountIdentifier: string): AccessTokenCacheItem {
// Get all access tokens with matching authority, resource id and home account ID
const tokenCacheItems: Array<AccessTokenCacheItem> = this.cacheManager.getAllAccessTokens(this.config.authOptions.clientId, authorityUri || "", resourceId || "", homeAccountIdentifier || "");
const tokenCacheItems: Array<AccessTokenCacheItem> = this.spaCacheManager.getAllAccessTokens(this.config.authOptions.clientId, authorityUri || "", resourceId || "", homeAccountIdentifier || "");
if (tokenCacheItems.length === 0) {
throw ClientAuthError.createNoTokensFoundError(requestScopes.printScopes());
}
Expand Down Expand Up @@ -388,7 +388,7 @@ export class SPAClient extends BaseClient {
);

// Create response handler
const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheStorage, this.cacheManager, this.cryptoUtils, this.logger);
const responseHandler = new SPAResponseHandler(this.config.authOptions.clientId, this.cacheStorage, this.spaCacheManager, this.cryptoUtils, this.logger);
// Validate response. This function throws a server error if an error is returned by the server.
responseHandler.validateServerAuthorizationTokenResponse(acquiredTokenResponse.body);
// Return token response with given parameters
Expand All @@ -400,9 +400,9 @@ export class SPAClient extends BaseClient {

/**
* Creates refreshToken request and sends to given token endpoint.
* @param refreshTokenRequest
* @param tokenEndpoint
* @param refreshToken
* @param refreshTokenRequest
* @param tokenEndpoint
* @param refreshToken
*/
private async renewToken(refreshTokenRequest: TokenRenewParameters, tokenEndpoint: string, refreshToken: string): Promise<TokenResponse> {
// Initialize request parameters.
Expand Down
8 changes: 8 additions & 0 deletions lib/msal-common/src/config/ClientConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ const DEFAULT_STORAGE_IMPLEMENTATION: ICacheStorage = {
setItem: () => {
const notImplErr = "Storage interface - setItem() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
},
getSerializedCache: (): Promise<string> => {
const notImplErr = "Storage interface - getSerializedCache() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
},
setSerializedCache: () => {
const notImplErr = "Storage interface - setSerializedCache() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
}
};

Expand Down
6 changes: 5 additions & 1 deletion lib/msal-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// App Auth Modules and Configuration
export { SPAClient } from "./client/SPAClient";
export { AuthorizationCodeClient } from "./client/AuthorizationCodeClient";
export { AuthorizationCodeClient} from "./client/AuthorizationCodeClient";
export { DeviceCodeClient } from "./client/DeviceCodeClient";
export { RefreshTokenClient } from "./client/RefreshTokenClient";
export { AuthOptions, SystemOptions, LoggerOptions, TelemetryOptions } from "./config/ClientConfiguration";
Expand All @@ -13,6 +13,9 @@ export { Authority } from "./authority/Authority";
export { AuthorityFactory } from "./authority/AuthorityFactory";
// Cache
export { ICacheStorage } from "./cache/ICacheStorage";
export { UnifiedCacheManager } from "./unifiedCache/UnifiedCacheManager";
export { JsonCache, InMemoryCache } from "./unifiedCache/utils/CacheTypes";
export { Serializer } from "./unifiedCache/serialize/Serializer";
// Network Interface
export { INetworkModule, NetworkRequestOptions } from "./network/INetworkModule";
export { NetworkResponse } from "./network/NetworkManager";
Expand Down Expand Up @@ -43,3 +46,4 @@ export { ClientConfigurationError, ClientConfigurationErrorMessage } from "./err
// Constants and Utils
export { Constants, PromptValue, TemporaryCacheKeys, PersistentCacheKeys } from "./utils/Constants";
export { StringUtils } from "./utils/StringUtils";
export { StringDict } from "./utils/MsalTypes";
1 change: 1 addition & 0 deletions lib/msal-common/src/request/AuthorizationCodeUrlRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type AuthorizationCodeUrlRequest = {
/**
* Provides a hint about the tenant or domain that the user should use to sign in. The value
* of the domain hint is a registered domain for the tenant.
* TODO: Name this as "extraQueryParameters"
*/
domainHint?: string;

Expand Down
20 changes: 15 additions & 5 deletions lib/msal-common/src/response/AuthenticationResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@
* Licensed under the MIT License.
*/

import { StringDict } from "../utils/MsalTypes";

/**
* Result returned from the authority's token endpoint.
*/
export type AuthenticationResult = {
// TODO: Also consider making an external type and use this as internal
export class AuthenticationResult {
// TODO this is temp class, it will be updated.
accessToken: string;
refreshToken: string;
uniqueId: string; // TODO: Check applicability
tenantId: string; // TODO: Check applicability
scopes: Array<string>;
tokenType: string; // TODO: get rid of this if we can
idToken: string;
expiresOn: string;
};
idTokenClaims: StringDict;
accessToken: string;
expiresOn: Date;
extExpiresOn?: Date; // TODO: Check what this maps to in other libraries
userRequestState?: string; // TODO: remove, just check how state is handled in other libraries
familyId?: string; // TODO: Check wider audience
}
Loading