diff --git a/README.md b/README.md
index 2eebfab6bd..9fb45845a9 100644
--- a/README.md
+++ b/README.md
@@ -1,217 +1,55 @@
-Microsoft Authentication Library for JavaScript (MSAL.js)
-=========================================================
+# Microsoft Authentication Library for JavaScript (MSAL.js)
-| [Getting Started](https://docs.microsoft.com/en-us/azure/active-directory/develop/guidedsetups/active-directory-javascriptspa)| [AAD Docs](https://aka.ms/aaddevv2) | [Library Reference](https://htmlpreview.github.io/?https://raw.githubusercontent.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/classes/_useragentapplication_.useragentapplication.html) | [Support](README.md#community-help-and-support) | [Samples](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Samples)
-| --- | --- | --- | --- | --- |
+The Microsoft Authentication Library for JavaScript enables client-side JavaScript web applications, running in a web browser, to authenticate users using [Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview) for work and school accounts (AAD), Microsoft personal accounts (MSA), and social identity providers like Facebook, Google, LinkedIn, Microsoft accounts, etc. through [Azure AD B2C](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-overview#identity-providers) service. It also enables your app to get tokens to access [Microsoft Cloud](https://www.microsoft.com/enterprise) services such as [Microsoft Graph](https://graph.microsoft.io).
+## Repository
-The MSAL library for JavaScript enables client-side JavaScript web applications, running in a web browser, to authenticate users using [Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview) work and school accounts (AAD), Microsoft personal accounts (MSA) and social identity providers like Facebook, Google, LinkedIn, Microsoft accounts, etc. through [Azure AD B2C](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-overview#identity-providers) service. It also enables your app to get tokens to access [Microsoft Cloud](https://www.microsoft.com/enterprise) services such as [Microsoft Graph](https://graph.microsoft.io).
+### Core and wrapper libaries
-[](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-js)[](https://www.npmjs.com/package/msal)[](https://nodei.co/npm/msal/)
+The [`lib`](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib) folder contains the source code for all of our libraries.
+- [Microsoft Authentication Library for JavaScript](lib/msal-core/README.md): A browser-based, framework-agnostic core library that enables authentication and token acquisition with the Microsoft Identity platform in JavaScript applications. Implements the OAuth 2.0 [Implicit Grant Flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow), and is [OpenID-compliant](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc).
-## Installation
-Via NPM:
-
- npm install msal
-
-Via CDN:
-
-
-
-
-
-Internet Explorer does not have native `Promise` support, and so you will need to include a polyfill for promises such as `bluebird`.
-
-
-
-
-See here for more details on [supported browsers and known compatability issues](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q4-what-browsers-is-msaljs-supported-on).
-
-## Roadmap and What To Expect From This Library
-Msal support on Javascript is a collection of libraries. `msal-core` or just simply `msal`, is the framework agnostic core library. Once our core 1.x+ is stabilized, we are going to bring our `msal-angular` library with the latest 1.x improvements. We are planning to deprecate support for `msal-angularjs` based on usage trends of the framework and the library indicating increased adoption of Angular 2+ instead of Angular 1x. After our current libraries are up to standards, we will begin balancing new feature requests, with new platforms such as `react` and `node.js`.
-
-Our goal is to communicate extremely well with the community and to take their opinions into account. We would like to get to a monthly minor release schedule, with patches coming as often as needed. The level of communication, planning, and granularity we want to get to will be a work in progress.
-
-Please check our [roadmap](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki#roadmap) to see what we are working on and what we are tracking next.
-
-## OAuth 2.0 and the Implicit Flow
-Msal implements the [Implicit Grant Flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow), as defined by the OAuth 2.0 protocol and is [OpenID](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc) compliant.
-
-Our goal is that the library abstracts enough of the protocol away so that you can get plug and play authentication, but it is important to know and understand the implicit flow from a security perspective.
-The implicit flow runs in the context of a web browser which cannot manage client secrets securely. It is optimized for single page apps and has one less hop between client and server so tokens are returned directly to the browser. These aspects make it naturally less secure.
-These security concerns are mitigated per standard practices such as- use of short lived tokens (and so no refresh tokens are returned), the library requiring a registered redirect URI for the app, library matching the request and response with a unique nonce and state parameter.
-
-## Cache Storage
-
-We offer two methods of storage for Msal, `localStorage` and `sessionStorage`. Our recommendation is to use `sessionStorage` because it is more secure in storing tokens that are acquired by your users, but `localStorage` will give you Single Sign On across tabs and user sessions. We encourage you to explore the options and make the best decision for your application.
-
-### Use forceRefresh to skip cache
-If you would like to skip a cached token and go to the server, please pass in the boolean `forceRefresh` into the `AuthenticationParameters` object used to make a login / token request.
-
-**WARNING:** `forceRefresh` should not be used by default, because of the performance impact on your application. Relying on the cache will give your users a better experience. Skipping cache should only be used in scenarios where you know the current cached data does not have up to date information. Example: Admin tool to add roles to a user that needs to get a new token with updated roles.
-
-
-## Usage
-The example below walks you through how to login a user and acquire a token to be used for Microsoft's Graph Api.
-
-#### Prerequisite
-
-Before using MSAL.js you will need to [register an application in Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) to get a valid `clientId` for configuration, and to register the routes that your app will accept redirect traffic on.
-
-#### 1. Instantiate the UserAgentApplication
-
-`UserAgentApplication` can be configured with a variety of different options, detailed in our [Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/MSAL.js-1.0.0-api-release#configuration-options), but the only required parameter is `auth.clientId`.
-
-After instantiating your instance, if you plan on using a redirect flow (`loginRedirect` and `acquireTokenRedirect`), you must register a callback handlers using `handleRedirectCallback(authCallback)` where `authCallback = function(AuthError, AuthResponse)`. The callback function is called after the authentication request is completed either successfully or with a failure. This is not required for the popup flows since they return promises.
-
-```JavaScript
-import * as Msal from "msal";
-// if using cdn version, 'Msal' will be available in the global scope
-
-const msalConfig = {
- auth: {
- clientId: 'your_client_id'
- }
-};
-
-const msalInstance = new Msal.UserAgentApplication(msalConfig);
-
-msalInstance.handleRedirectCallback((error, response) => {
- // handle redirect response or error
-});
-```
-
-For details on the configuration options, read [Initializing client applications with MSAL.js](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications).
-
-#### 2. Login the user
-
-Your app must login the user with either the `loginPopup` or the `loginRedirect` method to establish user context.
-
-When the login methods are called and the authentication of the user is completed by the Azure AD service, an [id token](https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens) is returned which is used to identify the user with some basic information.
-
-When you login a user, you can pass in scopes that the user can pre consent to on login, however this is not required. Please note that consenting to scopes on login, does not return an access_token for these scopes, but gives you the opportunity to obtain a token silently with these scopes passed in, with no further interaction from the user.
-
-It is best practice to only request scopes you need when you need them, a concept called dynamic consent. While this can create more interactive consent for users in your application, it also reduces drop-off from users that may be uneasy granting a large list of permissions for features they are not yet using.
-
-AAD will only allow you to get consent for 3 resources at a time, although you can request many scopes within a resource.
-When the user makes a login request, you can pass in multiple resources and their corresponding scopes because AAD issues an idToken pre consenting those scopes. However acquireToken calls are valid only for one resource / multiple scopes. If you need to access multiple resources, please make separate acquireToken calls per resource.
-
-```JavaScript
-var loginRequest = {
- scopes: ["user.read", "mail.send"] // optional Array
-};
-
-msalInstance.loginPopup(loginRequest)
- .then(response => {
- // handle response
- })
- .catch(err => {
- // handle error
- });
-```
-
-#### 3. Get an access token to call an API
-
-In MSAL, you can get access tokens for the APIs your app needs to call using the `acquireTokenSilent` method which makes a silent request(without prompting the user with UI) to Azure AD to obtain an access token. The Azure AD service then returns an [access token](https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens) containing the user consented scopes to allow your app to securely call the API.
-
-You can use `acquireTokenRedirect` or `acquireTokenPopup` to initiate interactive requests, although, it is best practice to only show interactive experiences if you are unable to obtain a token silently due to interaction required errors. If you are using an interactive token call, it must match the login method used in your application. (`loginPopup`=> `acquireTokenPopup`, `loginRedirect` => `acquireTokenRedirect`).
-
-If the `acquireTokenSilent` call fails with an error of type `InteractionRequiredAuthError` you will need to initiate an interactive request. This could happen for many reasons including scopes that have been revoked, expired tokens, or password changes.
+- [Microsoft Authentication Library for Angular](lib/msal-angular/README.md) :
+A wrapper of the core library for apps using Angular framework.
-`acquireTokenSilent` will look for a valid token in the cache, and if it is close to expiring or does not exist, will automatically try to refresh it for you.
+- [Microsoft Authentication Library for AngularJS](lib/msal-angularjs/README.md) :
+A wrapper of the core library for apps using AngularJS framework.
-See [Request and Response Data Types](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/MSAL.js-1.0.0-api-release#signing-in-and-getting-tokens-with-msaljs) for reference.
+### Samples
-```JavaScript
-// if the user is already logged in you can acquire a token
-if (msalInstance.getAccount()) {
- var tokenRequest = {
- scopes: ["user.read", "mail.send"]
- };
- msalInstance.acquireTokenSilent(tokenRequest)
- .then(response => {
- // get access token from response
- // response.accessToken
- })
- .catch(err => {
- // could also check if err instance of InteractionRequiredAuthError if you can import the class.
- if (err.name === "InteractionRequiredAuthError") {
- return msalInstance.acquireTokenPopup(tokenRequest)
- .then(response => {
- // get access token from response
- // response.accessToken
- })
- .catch(err => {
- // handle error
- });
- }
- });
-} else {
- // user is not logged in, you will need to log them in to acquire a token
-}
-```
+The [`samples`](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples) folder contains sample applications for our libaries. A complete list of samples can be found [our our wiki](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Samples).
-#### 4. Use the token as a bearer in an HTTP request to call the Microsoft Graph or a Web API
+## Package versioning
-```JavaScript
-var headers = new Headers();
-var bearer = "Bearer " + token;
-headers.append("Authorization", bearer);
-var options = {
- method: "GET",
- headers: headers
-};
-var graphEndpoint = "https://graph.microsoft.com/v1.0/me";
-
-fetch(graphEndpoint, options)
- .then(resp => {
- //do something with response
- });
-```
-
-You can learn further details about MSAL.js functionality documented in the [MSAL Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki) and find complete [code samples](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Samples).
-
-## Wrapper libraries
-
-We provide preview versions of the following wrapper libraries as separate packages.
-
-- [Microsoft Authentication Library for Angular Preview](lib/msal-angular/README.md) :
-A wrapper of the core library for apps using Angular framework.
+All of our libraries follow [semantic versioning](https://semver.org). We recommend using the latest version of each libary to ensure you have the latest security patches and bug fixes.
-- [Microsoft Authentication Library for AngularJS Preview](lib/msal-angularjs/README.md) :
-A wrapper of the core library for apps using AngularJS framework.
+## Roadmap
-Please check the [Roadmap](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki#roadmap) for details on release plans.
+Please check the [roadmap](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki#roadmap) to see what we are working on and what we have planned for future releases.
## Community Help and Support
-- [FAQs](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs) for access to our frequently asked questions
-
-- [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) using "msal" and "msal.js" tag.
+- [GitHub Issues](../../issues) is the best place to ask questions, report bugs, and new request features.
-We highly recommend you ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
+- [FAQs](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs) for access to our frequently asked questions.
-- [GitHub Issues](../../issues) for reporting a bug or feature requests
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) using "msal" and "msal.js" tag.
-- [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory) to provide recommendations and/or feedback
+- [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory) to provide recommendations and/or feedback.
## Contribute
We enthusiastically welcome contributions and feedback. Please read the [contributing guide](contributing.md) before you begin.
-## Security Library
-
-This library controls how users sign-in and access services. We recommend you always take the latest version of our library in your app when possible. We use [semantic versioning](http://semver.org) so you can control the risk associated with updating your app. As an example, always downloading the latest minor version number (e.g. x.*y*.x) ensures you get the latest security and feature enhancements but our API surface remains the same. You can always see the latest version and release notes under the Releases tab of GitHub.
-
## Security Reporting
If you find a security issue with our libraries or services please report it to [secure@microsoft.com](mailto:secure@microsoft.com) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](http://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://technet.microsoft.com/en-us/security/dd252948) and subscribing to Security Advisory Alerts.
## License
-Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License (the "License");
+Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License (the "License").
## We Value and Adhere to the Microsoft Open Source Code of Conduct
diff --git a/lib/msal-core/README.md b/lib/msal-core/README.md
index fd29bf1d8a..7ba713bb45 100644
--- a/lib/msal-core/README.md
+++ b/lib/msal-core/README.md
@@ -11,15 +11,34 @@ The MSAL library for JavaScript enables client-side JavaScript web applications,
[](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-js)[](https://www.npmjs.com/package/msal)[](https://nodei.co/npm/msal/)
## Installation
-Via NPM:
+### Via NPM:
npm install msal
-Via CDN:
+### Via Latest Microsoft CDN Version:
-
-
+
+
+
+
+
+
+
+### Via Latest Microsoft CDN Version (with SRI Hash):
+
+
+
+
+
+
+
+
+
+#### Notes:
+- [Subresource Integrity (SRI)](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) attributes are optional in the script tag.
+- All hashes are unique to the version of MSAL. You can find the previous hashes on the [MSAL Wiki page](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/MSAL-JS-CDN-URL-and-SRI-Hash).
+- We recommend including the SRI Hash with all script tags when using `msal.js` or `msal.min.js` (including when using a third-party CDN). When providing the SRI Hash, you *must* also provide the `crossorigin="anonymous"` field in the same tag.
Internet Explorer does not have native `Promise` support, and so you will need to include a polyfill for promises such as `bluebird`.
@@ -65,7 +84,7 @@ After instantiating your instance, if you plan on using a redirect flow (`loginR
```JavaScript
import * as Msal from "msal";
// if using cdn version, 'Msal' will be available in the global scope
-
+
const msalConfig = {
auth: {
clientId: 'your_client_id'
@@ -189,26 +208,6 @@ You can learn further details about MSAL.js functionality documented in the [MSA
You can learn further details about MSAL.js functionality documented in the [MSAL Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki) and find complete [code samples](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Samples).
-## Community Help and Support
-
-- [FAQs](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs) for access to our frequently asked questions
-
-- [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) using "msal" and "msal.js" tag.
-
-We highly recommend you ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
-
-- [GitHub Issues](../../issues) for reporting a bug or feature requests
-
-- [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory) to provide recommendations and/or feedback
-
-## Contribute
-
-We enthusiastically welcome contributions and feedback. Please read the [contributing guide](contributing.md) before you begin.
-
-## Security Library
-
-This library controls how users sign-in and access services. We recommend you always take the latest version of our library in your app when possible. We use [semantic versioning](http://semver.org) so you can control the risk associated with updating your app. As an example, always downloading the latest minor version number (e.g. x.*y*.x) ensures you get the latest security and feature enhanements but our API surface remains the same. You can always see the latest version and release notes under the Releases tab of GitHub.
-
## Security Reporting
If you find a security issue with our libraries or services please report it to [secure@microsoft.com](mailto:secure@microsoft.com) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](http://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://technet.microsoft.com/en-us/security/dd252948) and subscribing to Security Advisory Alerts.
diff --git a/lib/msal-core/src/Configuration.ts b/lib/msal-core/src/Configuration.ts
index b1d1b0db67..1afa167ba9 100644
--- a/lib/msal-core/src/Configuration.ts
+++ b/lib/msal-core/src/Configuration.ts
@@ -9,6 +9,7 @@ import { TelemetryEmitter } from "./telemetry/TelemetryTypes";
/**
* Cache location options supported by MSAL are:
+ *
* - local storage: MSAL uses browsers local storage to store its cache
* - session storage: MSAL uses the browsers session storage to store its cache
*/
@@ -30,7 +31,6 @@ const NAVIGATE_FRAME_WAIT = 500;
* - redirectUri - The redirect URI of the application, this should be same as the value in the application registration portal.Defaults to `window.location.href`.
* - postLogoutRedirectUri - Used to redirect the user to this location after logout. Defaults to `window.location.href`.
* - navigateToLoginRequestUrl - Used to turn off default navigation to start page after login. Default is true. This is used only for redirect flows.
- *
*/
export type AuthOptions = {
clientId: string;
@@ -54,6 +54,7 @@ export type CacheOptions = {
/**
* Telemetry Config Options
+ *
* - applicationName - Name of the consuming apps application
* - applicationVersion - Verison of the consuming application
* - telemetryEmitter - Function where telemetry events are flushed to
@@ -95,6 +96,18 @@ export type FrameworkOptions = {
protectedResourceMap?: Map>;
};
+/**
+ * Options to specify communication between embedded (iframed) apps and the Top Frame
+ *
+ * - topFrameOrigin - origin check to restrict messages to the top frame origin only
+ * - consentNeeded - indicates if the library needs consent from the topframe to delegate interaction from the embedded (iframe) application
+ */
+export type BrokerOptions = {
+ topFrameOrigin?: string;
+ embeddedFrameOrigin?: string;
+ consentNeeded?: boolean;
+};
+
/**
* Use the configuration object to configure MSAL and initialize the UserAgentApplication.
*
@@ -103,20 +116,22 @@ export type FrameworkOptions = {
* - cache: this is where you configure cache location and whether to store cache in cookies
* - system: this is where you can configure the logger, frame timeout etc.
* - framework: this is where you can configure the running mode of angular. More to come here soon.
+ * - broker: this is where you can configure broker options if your application resides in an iframe and needs the topframe for interaction flows (redirect APIs only)
*/
export type Configuration = {
auth: AuthOptions,
cache?: CacheOptions,
system?: SystemOptions,
- framework?: FrameworkOptions
+ framework?: FrameworkOptions,
+ broker?: BrokerOptions
};
const DEFAULT_AUTH_OPTIONS: AuthOptions = {
clientId: "",
authority: null,
validateAuthority: true,
- redirectUri: () => UrlUtils.getDefaultRedirectUri(),
- postLogoutRedirectUri: () => UrlUtils.getDefaultRedirectUri(),
+ redirectUri: () => UrlUtils.getCurrentUri(),
+ postLogoutRedirectUri: () => UrlUtils.getCurrentUri(),
navigateToLoginRequestUrl: true
};
@@ -138,23 +153,30 @@ const DEFAULT_FRAMEWORK_OPTIONS: FrameworkOptions = {
protectedResourceMap: new Map>()
};
+const DEFAULT_BROKER_OPTIONS: BrokerOptions = {
+ topFrameOrigin: null,
+ embeddedFrameOrigin: null,
+ consentNeeded: false
+};
+
/**
* MSAL function that sets the default options when not explicitly configured from app developer
*
- * @param TAuthOptions
- * @param TCacheOptions
- * @param TSystemOptions
- * @param TFrameworkOptions
+ * @param AuthOptions
+ * @param CacheOptions
+ * @param SystemOptions
+ * @param FrameworkOptions
+ * @param BrokerOptions
*
- * @returns TConfiguration object
+ * @returns Configuration object
*/
-
-export function buildConfiguration({ auth, cache = {}, system = {}, framework = {}}: Configuration): Configuration {
+export function buildConfiguration({ auth, cache = {}, system = {}, framework = {}, broker = {}}: Configuration): Configuration {
const overlayedConfig: Configuration = {
auth: { ...DEFAULT_AUTH_OPTIONS, ...auth },
cache: { ...DEFAULT_CACHE_OPTIONS, ...cache },
system: { ...DEFAULT_SYSTEM_OPTIONS, ...system },
- framework: { ...DEFAULT_FRAMEWORK_OPTIONS, ...framework }
+ framework: { ...DEFAULT_FRAMEWORK_OPTIONS, ...framework },
+ broker: { ...DEFAULT_BROKER_OPTIONS, ...broker }
};
return overlayedConfig;
}
diff --git a/lib/msal-core/src/ServerRequestParameters.ts b/lib/msal-core/src/ServerRequestParameters.ts
index fd86d8e551..ba046b722b 100644
--- a/lib/msal-core/src/ServerRequestParameters.ts
+++ b/lib/msal-core/src/ServerRequestParameters.ts
@@ -137,7 +137,7 @@ export class ServerRequestParameters {
* @param request
*/
private validatePromptParameter (prompt: string) {
- if (!([PromptState.LOGIN, PromptState.SELECT_ACCOUNT, PromptState.CONSENT, PromptState.NONE].indexOf(prompt) >= 0)) {
+ if ([PromptState.LOGIN, PromptState.SELECT_ACCOUNT, PromptState.CONSENT, PromptState.NONE].indexOf(prompt) < 0) {
throw ClientConfigurationError.createInvalidPromptError(prompt);
}
}
diff --git a/lib/msal-core/src/Storage.ts b/lib/msal-core/src/Storage.ts
deleted file mode 100644
index e086668b85..0000000000
--- a/lib/msal-core/src/Storage.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License.
- */
-
-import { Constants, CacheKeys } from "./utils/Constants";
-import { AccessTokenCacheItem } from "./AccessTokenCacheItem";
-import { CacheLocation } from "./Configuration";
-import { ClientConfigurationError } from "./error/ClientConfigurationError";
-
-/**
- * @hidden
- */
-export class Storage {// Singleton
-
- private static instance: Storage;
- private localStorageSupported: boolean;
- private sessionStorageSupported: boolean;
- private cacheLocation: CacheLocation;
-
- constructor(cacheLocation: CacheLocation) {
- if (Storage.instance) {
- return Storage.instance;
- }
-
- this.cacheLocation = cacheLocation;
- this.localStorageSupported = typeof window[this.cacheLocation] !== "undefined" && window[this.cacheLocation] != null;
- this.sessionStorageSupported = typeof window[cacheLocation] !== "undefined" && window[cacheLocation] != null;
- Storage.instance = this;
- if (!this.localStorageSupported && !this.sessionStorageSupported) {
- throw ClientConfigurationError.createNoStorageSupportedError();
- }
-
- return Storage.instance;
- }
-
- // add value to storage
- setItem(key: string, value: string, enableCookieStorage?: boolean): void {
- if (window[this.cacheLocation]) {
- window[this.cacheLocation].setItem(key, value);
- }
- if (enableCookieStorage) {
- this.setItemCookie(key, value);
- }
- }
-
- // get one item by key from storage
- getItem(key: string, enableCookieStorage?: boolean): string {
- if (enableCookieStorage && this.getItemCookie(key)) {
- return this.getItemCookie(key);
- }
- if (window[this.cacheLocation]) {
- return window[this.cacheLocation].getItem(key);
- }
- return null;
- }
-
- // remove value from storage
- removeItem(key: string): void {
- if (window[this.cacheLocation]) {
- return window[this.cacheLocation].removeItem(key);
- }
- }
-
- // clear storage (remove all items from it)
- clear(): void {
- if (window[this.cacheLocation]) {
- return window[this.cacheLocation].clear();
- }
- }
-
- getAllAccessTokens(clientId: string, homeAccountIdentifier: string): Array {
- const results: Array = [];
- let accessTokenCacheItem: AccessTokenCacheItem;
- const storage = window[this.cacheLocation];
- if (storage) {
- let key: string;
- for (key in storage) {
- if (storage.hasOwnProperty(key)) {
- if (key.match(clientId) && key.match(homeAccountIdentifier)) {
- const value = this.getItem(key);
- if (value) {
- accessTokenCacheItem = new AccessTokenCacheItem(JSON.parse(key), JSON.parse(value));
- results.push(accessTokenCacheItem);
- }
- }
- }
- }
- }
-
- return results;
- }
-
- removeAcquireTokenEntries(state?: string): void {
- const storage = window[this.cacheLocation];
- if (storage) {
- let key: string;
- for (key in storage) {
- if (storage.hasOwnProperty(key)) {
- if ((key.indexOf(CacheKeys.AUTHORITY) !== -1 || key.indexOf(CacheKeys.ACQUIRE_TOKEN_ACCOUNT) !== 1) && (!state || key.indexOf(state) !== -1)) {
- const splitKey = key.split(Constants.resourceDelimiter);
- let state;
- if (splitKey.length > 1) {
- state = splitKey[1];
- }
- if (state && !this.tokenRenewalInProgress(state)) {
- this.removeItem(key);
- this.removeItem(Constants.renewStatus + state);
- this.removeItem(Constants.stateLogin);
- this.removeItem(Constants.stateAcquireToken);
- this.removeItem(Constants.nonceIdToken);
- this.removeItem(Constants.loginRequest);
- this.setItemCookie(key, "", -1);
- }
- }
- }
- }
- }
-
- this.clearCookie();
- }
-
- private tokenRenewalInProgress(stateValue: string): boolean {
- const storage = window[this.cacheLocation];
- const renewStatus = storage[Constants.renewStatus + stateValue];
- return !(!renewStatus || renewStatus !== Constants.tokenRenewStatusInProgress);
- }
-
- resetCacheItems(): void {
- const storage = window[this.cacheLocation];
- if (storage) {
- let key: string;
- for (key in storage) {
- if (storage.hasOwnProperty(key)) {
- if (key.indexOf(Constants.msal) !== -1) {
- this.removeItem(key);
- }
- }
- }
- this.removeAcquireTokenEntries();
- }
- }
-
- setItemCookie(cName: string, cValue: string, expires?: number): void {
- let cookieStr = cName + "=" + cValue + ";";
- if (expires) {
- const expireTime = this.getCookieExpirationTime(expires);
- cookieStr += "expires=" + expireTime + ";";
- }
-
- document.cookie = cookieStr;
- }
-
- getItemCookie(cName: string): string {
- const name = cName + "=";
- const ca = document.cookie.split(";");
- for (let i = 0; i < ca.length; i++) {
- let c = ca[i];
- while (c.charAt(0) === " ") {
- c = c.substring(1);
- }
- if (c.indexOf(name) === 0) {
- return c.substring(name.length, c.length);
- }
- }
- return "";
- }
-
- getCookieExpirationTime(cookieLifeDays: number): string {
- const today = new Date();
- const expr = new Date(today.getTime() + cookieLifeDays * 24 * 60 * 60 * 1000);
- return expr.toUTCString();
- }
-
- clearCookie(): void {
- this.setItemCookie(Constants.nonceIdToken, "", -1);
- this.setItemCookie(Constants.stateLogin, "", -1);
- this.setItemCookie(Constants.loginRequest, "", -1);
- this.setItemCookie(Constants.stateAcquireToken, "", -1);
- }
-
- /**
- * Create acquireTokenAccountKey to cache account object
- * @param accountId
- * @param state
- */
- static generateAcquireTokenAccountKey(accountId: any, state: string): string {
- return CacheKeys.ACQUIRE_TOKEN_ACCOUNT + Constants.resourceDelimiter +
- `${accountId}` + Constants.resourceDelimiter + `${state}`;
- }
-
- /**
- * Create authorityKey to cache authority
- * @param state
- */
- static generateAuthorityKey(state: string): string {
- return CacheKeys.AUTHORITY + Constants.resourceDelimiter + `${state}`;
- }
-}
diff --git a/lib/msal-core/src/UserAgentApplication.ts b/lib/msal-core/src/UserAgentApplication.ts
index 1fe3414218..f976b55aca 100644
--- a/lib/msal-core/src/UserAgentApplication.ts
+++ b/lib/msal-core/src/UserAgentApplication.ts
@@ -3,16 +3,16 @@
* Licensed under the MIT License.
*/
-import { AccessTokenCacheItem } from "./AccessTokenCacheItem";
-import { AccessTokenKey } from "./AccessTokenKey";
-import { AccessTokenValue } from "./AccessTokenValue";
+import { AccessTokenCacheItem } from "./cache/AccessTokenCacheItem";
+import { AccessTokenKey } from "./cache/AccessTokenKey";
+import { AccessTokenValue } from "./cache/AccessTokenValue";
import { ServerRequestParameters } from "./ServerRequestParameters";
import { Authority } from "./authority/Authority";
import { ClientInfo } from "./ClientInfo";
-import { Constants, InteractionType, libraryVersion } from "./utils/Constants";
+import { Constants, ServerHashParamKeys, InteractionType, libraryVersion, TemporaryCacheKeys, PersistentCacheKeys, RequestStatus } from "./utils/Constants";
import { IdToken } from "./IdToken";
import { Logger } from "./Logger";
-import { Storage } from "./Storage";
+import { AuthCache } from "./cache/AuthCache";
import { Account } from "./Account";
import { ScopeSet } from "./ScopeSet";
import { StringUtils } from "./utils/StringUtils";
@@ -32,6 +32,9 @@ import { InteractionRequiredAuthError } from "./error/InteractionRequiredAuthErr
import { AuthResponse, buildResponseStateOnly } from "./AuthResponse";
import TelemetryManager from "./telemetry/TelemetryManager";
import { TelemetryPlatform, TelemetryConfig } from "./telemetry/TelemetryTypes";
+import { MessageType, MessageHelper } from "./messaging/MessageHelper";
+import { MessageCache } from "./messaging/MessageCache";
+import { MessageListener } from "./messaging/MessageListener";
// default authority
const DEFAULT_AUTHORITY = "https://login.microsoftonline.com/common";
@@ -134,16 +137,18 @@ export class UserAgentApplication {
private telemetryManager: TelemetryManager;
// Cache and Account info referred across token grant flow
- protected cacheStorage: Storage;
+ protected cacheStorage: AuthCache;
private account: Account;
// state variables
- private loginInProgress: boolean;
- private acquireTokenInProgress: boolean;
private silentAuthenticationState: string;
private silentLogin: boolean;
private redirectCallbacksSet: boolean;
+ // message interface
+ private messageCache: MessageCache;
+ private messageListener: MessageListener;;
+
// Authority Functionality
protected authorityInstance: Authority;
@@ -212,16 +217,12 @@ export class UserAgentApplication {
// if no authority is passed, set the default: "https://login.microsoftonline.com/common"
this.authority = this.config.auth.authority || DEFAULT_AUTHORITY;
- // track login and acquireToken in progress
- this.loginInProgress = false;
- this.acquireTokenInProgress = false;
-
// cache keys msal - typescript throws an error if any value other than "localStorage" or "sessionStorage" is passed
- try {
- this.cacheStorage = new Storage(this.config.cache.cacheLocation);
- } catch (e) {
- throw ClientConfigurationError.createInvalidCacheLocationConfigError(this.config.cache.cacheLocation);
- }
+ this.cacheStorage = new AuthCache(this.clientId, this.config.cache.cacheLocation, this.inCookie);
+
+ // initialize the message interface
+ this.messageCache = new MessageCache(this.cacheStorage);
+ this.messageListener = new MessageListener(this.messageCache, this.logger, this.config.broker.embeddedFrameOrigin);
// Initialize window handling code
window.activeRenewals = {};
@@ -233,9 +234,23 @@ export class UserAgentApplication {
const urlHash = window.location.hash;
const urlContainsHash = UrlUtils.urlContainsHash(urlHash);
+ // read the hash stored through the topframe in redirect by delegation flow
+ const urlTopFrame = this.messageCache.read(MessageType.URL_TOP_FRAME);
+ const cachedUrlHash = this.messageCache.read(MessageType.HASH);
+
// On the server 302 - Redirect, handle this
if (!this.config.framework.isAngular && urlContainsHash && !WindowUtils.isInIframe() && !WindowUtils.isInPopup()) {
- this.handleAuthenticationResponse(urlHash);
+ // REDIRECT_IFRAMES: if we are in topframe, store the hash in the cache
+ if(urlTopFrame) {
+ MessageHelper.handleTopFrameRedirect(this.messageCache, urlTopFrame, urlHash, this.logger);
+ }
+ else {
+ this.handleAuthenticationResponse(urlHash);
+ }
+ }
+ // REDIRECT_IFRAMES: Handle the auth response on reload if the topframe redirected on the iframed app's behalf and saved the hash
+ else if (WindowUtils.isInIframe() && cachedUrlHash) {
+ this.handleAuthenticationResponse(cachedUrlHash);
}
}
@@ -269,7 +284,7 @@ export class UserAgentApplication {
// On the server 302 - Redirect, handle this
if (!this.config.framework.isAngular) {
- const cachedHash = this.cacheStorage.getItem(Constants.urlHash);
+ const cachedHash = this.cacheStorage.getItem(TemporaryCacheKeys.URL_HASH);
if (cachedHash) {
this.processCallBack(cachedHash, null);
}
@@ -376,9 +391,11 @@ export class UserAgentApplication {
*/
private acquireTokenInteractive(interactionType: InteractionType, isLoginCall: boolean, request?: AuthenticationParameters, resolve?: any, reject?: any): void {
+ const interactionProgress = this.cacheStorage.getItem(TemporaryCacheKeys.INTERACTION_STATUS);
+
// If already in progress, do not proceed
- if (this.loginInProgress || this.acquireTokenInProgress) {
- const thrownError = this.loginInProgress ? ClientAuthError.createLoginInProgressError() : ClientAuthError.createAcquireTokenInProgressError();
+ if (interactionProgress === RequestStatus.IN_PROGRESS) {
+ const thrownError = isLoginCall ? ClientAuthError.createLoginInProgressError() : ClientAuthError.createAcquireTokenInProgressError();
const stateOnlyResponse = buildResponseStateOnly(this.getAccountState(request && request.state));
this.authErrorHandler(interactionType,
thrownError,
@@ -446,13 +463,8 @@ export class UserAgentApplication {
*
*/
private acquireTokenHelper(account: Account, interactionType: InteractionType, isLoginCall: boolean, request?: AuthenticationParameters, scopes?: Array, resolve?: any, reject?: any): void {
- // Track the acquireToken progress
- if (isLoginCall) {
- this.loginInProgress = true;
- } else {
- this.acquireTokenInProgress = true;
- }
-
+ // Track the acquireToken progress
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.IN_PROGRESS);
const scope = scopes ? scopes.join(" ").toLowerCase() : this.clientId.toLowerCase();
let serverAuthenticationRequest: ServerRequestParameters;
@@ -467,12 +479,11 @@ export class UserAgentApplication {
// Push popup window handle onto stack for tracking
WindowUtils.trackPopup(popUpWindow);
} catch (e) {
- this.loginInProgress = false;
- this.acquireTokenInProgress = false;
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.CANCELLED);
this.logger.info(ClientAuthErrorMessage.popUpWindowError.code + ":" + ClientAuthErrorMessage.popUpWindowError.desc);
- this.cacheStorage.setItem(Constants.msalError, ClientAuthErrorMessage.popUpWindowError.code);
- this.cacheStorage.setItem(Constants.msalErrorDescription, ClientAuthErrorMessage.popUpWindowError.desc);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR, ClientAuthErrorMessage.popUpWindowError.code);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR_DESC, ClientAuthErrorMessage.popUpWindowError.desc);
if (reject) {
reject(ClientAuthError.createPopupWindowError());
}
@@ -490,11 +501,11 @@ export class UserAgentApplication {
if (isLoginCall) {
// if the user sets the login start page - angular only??
- loginStartPage = this.cacheStorage.getItem(Constants.angularLoginRequest);
+ loginStartPage = this.cacheStorage.getItem(TemporaryCacheKeys.ANGULAR_LOGIN_REQUEST);
if (!loginStartPage || loginStartPage === "") {
loginStartPage = window.location.href;
} else {
- this.cacheStorage.setItem(Constants.angularLoginRequest, "");
+ this.cacheStorage.setItem(TemporaryCacheKeys.ANGULAR_LOGIN_REQUEST, "");
}
}
@@ -518,7 +529,7 @@ export class UserAgentApplication {
// set state in cache
if (interactionType === Constants.interactionTypeRedirect) {
if (!isLoginCall) {
- this.cacheStorage.setItem(Constants.stateAcquireToken, serverAuthenticationRequest.state, this.inCookie);
+ this.cacheStorage.setItem(TemporaryCacheKeys.STATE_ACQ_TOKEN, serverAuthenticationRequest.state, this.inCookie);
}
} else if (interactionType === Constants.interactionTypePopup) {
window.renewStates.push(serverAuthenticationRequest.state);
@@ -530,8 +541,14 @@ export class UserAgentApplication {
throw ClientAuthError.createInvalidInteractionTypeError();
}
- // prompt user for interaction
- this.navigateWindow(urlNavigate, popUpWindow);
+ // IFRAMEDAPPS: if we are redirecting in an iframe, post a message to the topFrame
+ if(WindowUtils.isInIframe() && !popUpWindow) {
+ MessageHelper.redirectDelegationRequest(this.messageCache, urlNavigate, this.config.broker.topFrameOrigin);
+ }
+ else {
+ // prompt user for interaction
+ WindowUtils.navigateWindow(urlNavigate, this.logger, popUpWindow);
+ }
// popUpWindow will be null for redirects, so we dont need to attempt to monitor the window
if (popUpWindow) {
@@ -540,8 +557,8 @@ export class UserAgentApplication {
// Hash found
this.handleAuthenticationResponse(hash);
- this.loginInProgress = false;
- this.acquireTokenInProgress = false;
+ // Request completed successfully, set to completed
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.COMPLETED);
this.logger.info("Closing popup window");
// TODO: Check how this can be extracted for any framework specific code?
@@ -560,10 +577,11 @@ export class UserAgentApplication {
return;
}
- this.loginInProgress = false;
- this.acquireTokenInProgress = false;
+ // Request failed, set to canceled
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.CANCELLED);
}
}
+
}).catch((err) => {
this.logger.warning("could not resolve endpoints");
this.authErrorHandler(interactionType, ClientAuthError.createEndpointResolutionError(err.toString), buildResponseStateOnly(request.state), reject);
@@ -750,8 +768,7 @@ export class UserAgentApplication {
return popupWindow;
} catch (e) {
this.logger.error("error opening popup " + e.message);
- this.loginInProgress = false;
- this.acquireTokenInProgress = false;
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.CANCELLED);
throw ClientAuthError.createPopupWindowError(e.toString());
}
}
@@ -770,9 +787,9 @@ export class UserAgentApplication {
// set iframe session to pending
const expectedState = window.activeRenewals[scope];
this.logger.verbose("Set loading state to pending for: " + scope + ":" + expectedState);
- this.cacheStorage.setItem(Constants.renewStatus + expectedState, Constants.tokenRenewStatusInProgress);
+ this.cacheStorage.setItem(TemporaryCacheKeys.RENEW_STATUS + expectedState, RequestStatus.IN_PROGRESS);
setTimeout(() => {
- if (this.cacheStorage.getItem(Constants.renewStatus + expectedState) === Constants.tokenRenewStatusInProgress) {
+ if (this.cacheStorage.getItem(TemporaryCacheKeys.RENEW_STATUS + expectedState) === RequestStatus.IN_PROGRESS) {
// fail the iframe session if it's in pending state
this.logger.verbose("Loading frame has timed out after: " + (this.config.system.loadFrameTimeout / 1000) + " seconds for scope " + scope + ":" + expectedState);
// Error after timeout
@@ -780,7 +797,7 @@ export class UserAgentApplication {
window.callbackMappedToRenewStates[expectedState](null, ClientAuthError.createTokenRenewalTimeoutError());
}
- this.cacheStorage.setItem(Constants.renewStatus + expectedState, Constants.tokenRenewStatusCancelled);
+ this.cacheStorage.setItem(TemporaryCacheKeys.RENEW_STATUS + expectedState, RequestStatus.CANCELLED);
}
}, this.config.system.loadFrameTimeout);
@@ -795,25 +812,6 @@ export class UserAgentApplication {
// #region General Helpers
- /**
- * @hidden
- * Used to redirect the browser to the STS authorization endpoint
- * @param {string} urlNavigate - URL of the authorization endpoint
- */
- private navigateWindow(urlNavigate: string, popupWindow?: Window) {
- // Navigate if valid URL
- if (urlNavigate && !StringUtils.isEmpty(urlNavigate)) {
- const navigateWindow: Window = popupWindow ? popupWindow : window;
- const logMessage: string = popupWindow ? "Navigated Popup window to:" + urlNavigate : "Navigate to:" + urlNavigate;
- this.logger.infoPii(logMessage);
- navigateWindow.location.replace(urlNavigate);
- }
- else {
- this.logger.info("Navigate url is empty");
- throw AuthError.createUnexpectedError("Navigate url is empty");
- }
- }
-
/**
* @hidden
* Used to add the developer requested callback to the array of callbacks for the specified scopes. The updated array is stored on the window object
@@ -882,7 +880,7 @@ export class UserAgentApplication {
const urlNavigate = authority.EndSessionEndpoint
? `${authority.EndSessionEndpoint}?${logout}`
: `${this.authority}oauth2/v2.0/logout?${logout}`;
- this.navigateWindow(urlNavigate);
+ WindowUtils.navigateWindow(urlNavigate, this.logger);
});
}
@@ -898,7 +896,7 @@ export class UserAgentApplication {
this.cacheStorage.removeItem(JSON.stringify(accessTokenItems[i].key));
}
this.cacheStorage.resetCacheItems();
- this.cacheStorage.clearCookie();
+ this.cacheStorage.clearMsalCookie();
}
/**
@@ -955,11 +953,11 @@ export class UserAgentApplication {
}
// remove hash from the cache
- this.cacheStorage.removeItem(Constants.urlHash);
+ this.cacheStorage.removeItem(TemporaryCacheKeys.URL_HASH);
try {
// Clear the cookie in the hash
- this.cacheStorage.clearCookie();
+ this.cacheStorage.clearMsalCookie(stateInfo.state);
const accountState: string = this.getAccountState(stateInfo.state);
if (response) {
if ((stateInfo.requestType === Constants.renewToken) || response.accessToken) {
@@ -968,10 +966,10 @@ export class UserAgentApplication {
} else {
this.logger.verbose("acquiring token interactive in progress");
}
- response.tokenType = Constants.accessToken;
+ response.tokenType = ServerHashParamKeys.ACCESS_TOKEN;
}
else if (stateInfo.requestType === Constants.login) {
- response.tokenType = Constants.idToken;
+ response.tokenType = ServerHashParamKeys.ID_TOKEN;
}
if (!parentCallback) {
this.authResponseHandler(Constants.interactionTypeRedirect, response);
@@ -1018,9 +1016,9 @@ export class UserAgentApplication {
tokenResponseCallback = null;
// if set to navigate to loginRequest page post login
if (this.config.auth.navigateToLoginRequestUrl) {
- this.cacheStorage.setItem(Constants.urlHash, locationHash);
+ this.cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, locationHash);
if (window.parent === window) {
- window.location.href = this.cacheStorage.getItem(Constants.loginRequest, this.inCookie);
+ window.location.href = this.cacheStorage.getItem(TemporaryCacheKeys.LOGIN_REQUEST, this.inCookie);
}
return;
}
@@ -1030,7 +1028,7 @@ export class UserAgentApplication {
if (!this.redirectCallbacksSet) {
// We reached this point too early - cache hash, return and process in handleRedirectCallbacks
- this.cacheStorage.setItem(Constants.urlHash, locationHash);
+ this.cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, locationHash);
return;
}
}
@@ -1071,13 +1069,13 @@ export class UserAgentApplication {
*/
// loginRedirect
- if (stateResponse.state === this.cacheStorage.getItem(Constants.stateLogin, this.inCookie) || stateResponse.state === this.silentAuthenticationState) { // loginRedirect
+ if (stateResponse.state === this.cacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN, this.inCookie) || stateResponse.state === this.silentAuthenticationState) { // loginRedirect
stateResponse.requestType = Constants.login;
stateResponse.stateMatch = true;
return stateResponse;
}
// acquireTokenRedirect
- else if (stateResponse.state === this.cacheStorage.getItem(Constants.stateAcquireToken, this.inCookie)) { // acquireTokenRedirect
+ else if (stateResponse.state === this.cacheStorage.getItem(TemporaryCacheKeys.STATE_ACQ_TOKEN, this.inCookie)) { // acquireTokenRedirect
stateResponse.requestType = Constants.renewToken;
stateResponse.stateMatch = true;
return stateResponse;
@@ -1192,7 +1190,7 @@ export class UserAgentApplication {
const response : AuthResponse = {
uniqueId: "",
tenantId: "",
- tokenType: (accessTokenCacheItem.value.idToken === accessTokenCacheItem.value.accessToken) ? Constants.idToken : Constants.accessToken,
+ tokenType: (accessTokenCacheItem.value.idToken === accessTokenCacheItem.value.accessToken) ? ServerHashParamKeys.ID_TOKEN : ServerHashParamKeys.ACCESS_TOKEN,
idToken: idTokenObj,
idTokenClaims: idTokenObj.claims,
accessToken: accessTokenCacheItem.value.accessToken,
@@ -1321,9 +1319,9 @@ export class UserAgentApplication {
let expiration: number;
// if the response contains "scope"
- if (parameters.hasOwnProperty("scope")) {
+ if (parameters.hasOwnProperty(ServerHashParamKeys.SCOPE)) {
// read the scopes
- scope = parameters["scope"];
+ scope = parameters[ServerHashParamKeys.SCOPE];
const consentedScopes = scope.split(" ");
// retrieve all access tokens from the cache, remove the dup scores
@@ -1341,14 +1339,14 @@ export class UserAgentApplication {
}
// Generate and cache accessTokenKey and accessTokenValue
- const expiresIn = TimeUtils.parseExpiresIn(parameters[Constants.expiresIn]);
+ const expiresIn = TimeUtils.parseExpiresIn(parameters[ServerHashParamKeys.EXPIRES_IN]);
expiration = TimeUtils.now() + expiresIn;
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientObj.uid, clientObj.utid);
- const accessTokenValue = new AccessTokenValue(parameters[Constants.accessToken], idTokenObj.rawIdToken, expiration.toString(), clientInfo);
+ const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ACCESS_TOKEN], idTokenObj.rawIdToken, expiration.toString(), clientInfo);
this.cacheStorage.setItem(JSON.stringify(accessTokenKey), JSON.stringify(accessTokenValue));
- accessTokenResponse.accessToken = parameters[Constants.accessToken];
+ accessTokenResponse.accessToken = parameters[ServerHashParamKeys.ACCESS_TOKEN];
accessTokenResponse.scopes = consentedScopes;
}
// if the response does not contain "scope" - scope is usually client_id and the token will be id_token
@@ -1358,10 +1356,10 @@ export class UserAgentApplication {
// Generate and cache accessTokenKey and accessTokenValue
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientObj.uid, clientObj.utid);
expiration = Number(idTokenObj.expiration);
- const accessTokenValue = new AccessTokenValue(parameters[Constants.idToken], parameters[Constants.idToken], expiration.toString(), clientInfo);
+ const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ID_TOKEN], parameters[ServerHashParamKeys.ID_TOKEN], expiration.toString(), clientInfo);
this.cacheStorage.setItem(JSON.stringify(accessTokenKey), JSON.stringify(accessTokenValue));
accessTokenResponse.scopes = [scope];
- accessTokenResponse.accessToken = parameters[Constants.idToken];
+ accessTokenResponse.accessToken = parameters[ServerHashParamKeys.ID_TOKEN];
}
if (expiration) {
@@ -1401,22 +1399,20 @@ export class UserAgentApplication {
let idTokenObj: IdToken = null;
// If server returns an error
- if (hashParams.hasOwnProperty(Constants.errorDescription) || hashParams.hasOwnProperty(Constants.error)) {
- this.logger.infoPii("Error :" + hashParams[Constants.error] + "; Error description:" + hashParams[Constants.errorDescription]);
- this.cacheStorage.setItem(Constants.msalError, hashParams[Constants.error]);
- this.cacheStorage.setItem(Constants.msalErrorDescription, hashParams[Constants.errorDescription]);
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.ERROR_DESCRIPTION) || hashParams.hasOwnProperty(ServerHashParamKeys.ERROR)) {
+ this.logger.infoPii("Error :" + hashParams[ServerHashParamKeys.ERROR] + "; Error description:" + hashParams[ServerHashParamKeys.ERROR_DESCRIPTION]);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR, hashParams[ServerHashParamKeys.ERROR]);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR_DESC, hashParams[ServerHashParamKeys.ERROR_DESCRIPTION]);
// login
if (stateInfo.requestType === Constants.login) {
- this.loginInProgress = false;
- this.cacheStorage.setItem(Constants.loginError, hashParams[Constants.errorDescription] + ":" + hashParams[Constants.error]);
- authorityKey = Storage.generateAuthorityKey(stateInfo.state);
+ this.cacheStorage.setItem(PersistentCacheKeys.LOGIN_ERROR, hashParams[ServerHashParamKeys.ERROR_DESCRIPTION] + ":" + hashParams[ServerHashParamKeys.ERROR]);
+ authorityKey = AuthCache.generateAuthorityKey(stateInfo.state);
}
// acquireToken
if (stateInfo.requestType === Constants.renewToken) {
- this.acquireTokenInProgress = false;
- authorityKey = Storage.generateAuthorityKey(stateInfo.state);
+ authorityKey = AuthCache.generateAuthorityKey(stateInfo.state);
const account: Account = this.getAccount();
let accountId;
@@ -1428,18 +1424,18 @@ export class UserAgentApplication {
accountId = Constants.no_account;
}
- acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(accountId, stateInfo.state);
+ acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(accountId, stateInfo.state);
}
const {
- [Constants.error]: hashErr,
- [Constants.errorDescription]: hashErrDesc
+ [ServerHashParamKeys.ERROR]: hashErr,
+ [ServerHashParamKeys.ERROR_DESCRIPTION]: hashErrDesc
} = hashParams;
if (InteractionRequiredAuthError.isInteractionRequiredError(hashErr) ||
InteractionRequiredAuthError.isInteractionRequiredError(hashErrDesc)) {
- error = new InteractionRequiredAuthError(hashParams[Constants.error], hashParams[Constants.errorDescription]);
+ error = new InteractionRequiredAuthError(hashParams[ServerHashParamKeys.ERROR], hashParams[ServerHashParamKeys.ERROR_DESCRIPTION]);
} else {
- error = new ServerError(hashParams[Constants.error], hashParams[Constants.errorDescription]);
+ error = new ServerError(hashParams[ServerHashParamKeys.ERROR], hashParams[ServerHashParamKeys.ERROR_DESCRIPTION]);
}
}
// If the server returns "Success"
@@ -1447,25 +1443,24 @@ export class UserAgentApplication {
// Verify the state from redirect and record tokens to storage if exists
if (stateInfo.stateMatch) {
this.logger.info("State is right");
- if (hashParams.hasOwnProperty(Constants.sessionState)) {
- this.cacheStorage.setItem(Constants.msalSessionState, hashParams[Constants.sessionState]);
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.SESSION_STATE)) {
+ this.cacheStorage.setItem(TemporaryCacheKeys.SESSION_STATE, hashParams[ServerHashParamKeys.SESSION_STATE]);
}
response.accountState = this.getAccountState(stateInfo.state);
let clientInfo: string = "";
// Process access_token
- if (hashParams.hasOwnProperty(Constants.accessToken)) {
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN)) {
this.logger.info("Fragment has access token");
- this.acquireTokenInProgress = false;
// retrieve the id_token from response if present
- if (hashParams.hasOwnProperty(Constants.idToken)) {
- idTokenObj = new IdToken(hashParams[Constants.idToken]);
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)) {
+ idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);
response.idToken = idTokenObj;
response.idTokenClaims = idTokenObj.claims;
} else {
- idTokenObj = new IdToken(this.cacheStorage.getItem(Constants.idTokenKey));
+ idTokenObj = new IdToken(this.cacheStorage.getItem(PersistentCacheKeys.IDTOKEN));
response = ResponseUtils.setResponseIdToken(response, idTokenObj);
}
@@ -1473,8 +1468,8 @@ export class UserAgentApplication {
const authority: string = this.populateAuthority(stateInfo.state, this.inCookie, this.cacheStorage, idTokenObj);
// retrieve client_info - if it is not found, generate the uid and utid from idToken
- if (hashParams.hasOwnProperty(Constants.clientInfo)) {
- clientInfo = hashParams[Constants.clientInfo];
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.CLIENT_INFO)) {
+ clientInfo = hashParams[ServerHashParamKeys.CLIENT_INFO];
} else {
this.logger.warning("ClientInfo not received in the response from AAD");
throw ClientAuthError.createClientInfoNotPopulatedError("ClientInfo not received in the response from the server");
@@ -1490,8 +1485,8 @@ export class UserAgentApplication {
accountKey = Constants.no_account;
}
- acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(accountKey, stateInfo.state);
- const acquireTokenAccountKey_noaccount = Storage.generateAcquireTokenAccountKey(Constants.no_account, stateInfo.state);
+ acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(accountKey, stateInfo.state);
+ const acquireTokenAccountKey_noaccount = AuthCache.generateAcquireTokenAccountKey(Constants.no_account, stateInfo.state);
const cachedAccount: string = this.cacheStorage.getItem(acquireTokenAccountKey);
let acquireTokenAccount: Account;
@@ -1514,18 +1509,15 @@ export class UserAgentApplication {
}
// Process id_token
- if (hashParams.hasOwnProperty(Constants.idToken)) {
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)) {
this.logger.info("Fragment has id token");
- // login no longer in progress
- this.loginInProgress = false;
-
// set the idToken
- idTokenObj = new IdToken(hashParams[Constants.idToken]);
+ idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);
response = ResponseUtils.setResponseIdToken(response, idTokenObj);
- if (hashParams.hasOwnProperty(Constants.clientInfo)) {
- clientInfo = hashParams[Constants.clientInfo];
+ if (hashParams.hasOwnProperty(ServerHashParamKeys.CLIENT_INFO)) {
+ clientInfo = hashParams[ServerHashParamKeys.CLIENT_INFO];
} else {
this.logger.warning("ClientInfo not received in the response from AAD");
}
@@ -1538,16 +1530,16 @@ export class UserAgentApplication {
if (idTokenObj && idTokenObj.nonce) {
// check nonce integrity if idToken has nonce - throw an error if not matched
- if (idTokenObj.nonce !== this.cacheStorage.getItem(Constants.nonceIdToken, this.inCookie)) {
+ if (idTokenObj.nonce !== this.cacheStorage.getItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${stateInfo.state}`, this.inCookie)) {
this.account = null;
- this.cacheStorage.setItem(Constants.loginError, "Nonce Mismatch. Expected Nonce: " + this.cacheStorage.getItem(Constants.nonceIdToken, this.inCookie) + "," + "Actual Nonce: " + idTokenObj.nonce);
- this.logger.error("Nonce Mismatch.Expected Nonce: " + this.cacheStorage.getItem(Constants.nonceIdToken, this.inCookie) + "," + "Actual Nonce: " + idTokenObj.nonce);
- error = ClientAuthError.createNonceMismatchError(this.cacheStorage.getItem(Constants.nonceIdToken, this.inCookie), idTokenObj.nonce);
+ this.cacheStorage.setItem(PersistentCacheKeys.LOGIN_ERROR, "Nonce Mismatch. Expected Nonce: " + this.cacheStorage.getItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${stateInfo.state}`, this.inCookie) + "," + "Actual Nonce: " + idTokenObj.nonce);
+ this.logger.error("Nonce Mismatch.Expected Nonce: " + this.cacheStorage.getItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${stateInfo.state}`, this.inCookie) + "," + "Actual Nonce: " + idTokenObj.nonce);
+ error = ClientAuthError.createNonceMismatchError(this.cacheStorage.getItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${stateInfo.state}`, this.inCookie), idTokenObj.nonce);
}
// Save the token
else {
- this.cacheStorage.setItem(Constants.idTokenKey, hashParams[Constants.idToken]);
- this.cacheStorage.setItem(Constants.msalClientInfo, clientInfo);
+ this.cacheStorage.setItem(PersistentCacheKeys.IDTOKEN, hashParams[ServerHashParamKeys.ID_TOKEN]);
+ this.cacheStorage.setItem(PersistentCacheKeys.CLIENT_INFO, clientInfo);
// Save idToken as access token for app itself
this.saveAccessToken(response, authority, hashParams, clientInfo, idTokenObj);
@@ -1558,8 +1550,8 @@ export class UserAgentApplication {
this.logger.error("Invalid id_token received in the response");
error = ClientAuthError.createInvalidIdTokenError(idTokenObj);
- this.cacheStorage.setItem(Constants.msalError, error.errorCode);
- this.cacheStorage.setItem(Constants.msalErrorDescription, error.errorMessage);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR, error.errorCode);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR_DESC, error.errorMessage);
}
}
}
@@ -1568,28 +1560,35 @@ export class UserAgentApplication {
authorityKey = stateInfo.state;
acquireTokenAccountKey = stateInfo.state;
- const expectedState = this.cacheStorage.getItem(Constants.stateLogin, this.inCookie);
+ const expectedState = this.cacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN, this.inCookie);
this.logger.error("State Mismatch.Expected State: " + expectedState + "," + "Actual State: " + stateInfo.state);
error = ClientAuthError.createInvalidStateError(stateInfo.state, expectedState);
- this.cacheStorage.setItem(Constants.msalError, error.errorCode);
- this.cacheStorage.setItem(Constants.msalErrorDescription, error.errorMessage);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR, error.errorCode);
+ this.cacheStorage.setItem(PersistentCacheKeys.ERROR_DESC, error.errorMessage);
}
}
-
- this.cacheStorage.setItem(Constants.renewStatus + stateInfo.state, Constants.tokenRenewStatusCompleted);
+
+ // Set status to completed
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.COMPLETED);
+ this.cacheStorage.setItem(TemporaryCacheKeys.RENEW_STATUS + stateInfo.state, RequestStatus.COMPLETED);
this.cacheStorage.removeAcquireTokenEntries(stateInfo.state);
// this is required if navigateToLoginRequestUrl=false
if (this.inCookie) {
this.cacheStorage.setItemCookie(authorityKey, "", -1);
- this.cacheStorage.clearCookie();
+ this.cacheStorage.clearMsalCookie(stateInfo.state);
}
if (error) {
+ // Error case, set status to cancelled
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.CANCELLED);
+ this.cacheStorage.setItem(TemporaryCacheKeys.RENEW_STATUS + stateInfo.state, RequestStatus.CANCELLED);
+ this.cacheStorage.removeAcquireTokenEntries(stateInfo.state);
throw error;
}
if (!response) {
throw AuthError.createUnexpectedError("Response is null");
}
+
return response;
}
@@ -1601,8 +1600,8 @@ export class UserAgentApplication {
* @param idTokenObj
* @param response
*/
- private populateAuthority(state: string, inCookie: boolean, cacheStorage: Storage, idTokenObj: IdToken): string {
- const authorityKey: string = Storage.generateAuthorityKey(state);
+ private populateAuthority(state: string, inCookie: boolean, cacheStorage: AuthCache, idTokenObj: IdToken): string {
+ const authorityKey: string = AuthCache.generateAuthorityKey(state);
const cachedAuthority: string = cacheStorage.getItem(authorityKey, inCookie);
// retrieve the authority from cache and replace with tenantID
@@ -1628,8 +1627,8 @@ export class UserAgentApplication {
}
// frame is used to get idToken and populate the account for the given session
- const rawIdToken = this.cacheStorage.getItem(Constants.idTokenKey);
- const rawClientInfo = this.cacheStorage.getItem(Constants.msalClientInfo);
+ const rawIdToken = this.cacheStorage.getItem(PersistentCacheKeys.IDTOKEN);
+ const rawClientInfo = this.cacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO);
if (!StringUtils.isEmpty(rawIdToken) && !StringUtils.isEmpty(rawClientInfo)) {
const idToken = new IdToken(rawIdToken);
@@ -1885,12 +1884,12 @@ export class UserAgentApplication {
* Return boolean flag to developer to help inform if login is in progress
* @returns {boolean} true/false
*/
- public getLoginInProgress(): boolean {
- const pendingCallback = this.cacheStorage.getItem(Constants.urlHash);
+ public isInteractionInProgress(): boolean {
+ const pendingCallback = this.cacheStorage.getItem(TemporaryCacheKeys.URL_HASH);
if (pendingCallback) {
return true;
}
- return this.loginInProgress;
+ return this.cacheStorage.getItem(TemporaryCacheKeys.INTERACTION_STATUS) === RequestStatus.IN_PROGRESS;
}
/**
@@ -1899,28 +1898,12 @@ export class UserAgentApplication {
*
* @param loginInProgress
*/
- protected setloginInProgress(loginInProgress : boolean) {
- this.loginInProgress = loginInProgress;
- }
-
- /**
- * @hidden
- * @ignore
- *
- * returns the status of acquireTokenInProgress
- */
- protected getAcquireTokenInProgress(): boolean {
- return this.acquireTokenInProgress;
- }
-
- /**
- * @hidden
- * @ignore
- *
- * @param acquireTokenInProgress
- */
- protected setAcquireTokenInProgress(acquireTokenInProgress : boolean) {
- this.acquireTokenInProgress = acquireTokenInProgress;
+ protected setInteractionInProgress(inProgress: boolean) {
+ if (inProgress) {
+ this.cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.IN_PROGRESS);
+ } else {
+ this.cacheStorage.removeItem(TemporaryCacheKeys.INTERACTION_STATUS);
+ }
}
/**
@@ -2009,7 +1992,6 @@ export class UserAgentApplication {
*
*/
private getTokenType(accountObject: Account, scopes: string[], silentCall: boolean): string {
-
/*
* if account is passed and matches the account object/or set to getAccount() from cache
* if client-id is passed as scope, get id_token else token/id_token_token (in case no session exists)
@@ -2055,7 +2037,7 @@ export class UserAgentApplication {
// Cache acquireTokenAccountKey
const accountId = account ? this.getAccountId(account) : Constants.no_account;
- const acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(accountId, state);
+ const acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(accountId, state);
this.cacheStorage.setItem(acquireTokenAccountKey, JSON.stringify(account));
}
@@ -2070,7 +2052,7 @@ export class UserAgentApplication {
*/
private setAuthorityCache(state: string, authority: string) {
// Cache authorityKey
- const authorityKey = Storage.generateAuthorityKey(state);
+ const authorityKey = AuthCache.generateAuthorityKey(state);
this.cacheStorage.setItem(authorityKey, UrlUtils.CanonicalizeUri(authority), this.inCookie);
}
@@ -2085,8 +2067,8 @@ export class UserAgentApplication {
// Cache account and authority
if (loginStartPage) {
// Cache the state, nonce, and login request data
- this.cacheStorage.setItem(Constants.loginRequest, loginStartPage, this.inCookie);
- this.cacheStorage.setItem(Constants.stateLogin, serverAuthenticationRequest.state, this.inCookie);
+ this.cacheStorage.setItem(TemporaryCacheKeys.LOGIN_REQUEST, loginStartPage, this.inCookie);
+ this.cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, serverAuthenticationRequest.state, this.inCookie);
} else {
this.setAccountCache(account, serverAuthenticationRequest.state);
}
@@ -2094,7 +2076,7 @@ export class UserAgentApplication {
this.setAuthorityCache(serverAuthenticationRequest.state, serverAuthenticationRequest.authority);
// Cache nonce
- this.cacheStorage.setItem(Constants.nonceIdToken, serverAuthenticationRequest.nonce, this.inCookie);
+ this.cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${serverAuthenticationRequest.state}`, serverAuthenticationRequest.nonce, this.inCookie);
}
/**
diff --git a/lib/msal-core/src/AccessTokenCacheItem.ts b/lib/msal-core/src/cache/AccessTokenCacheItem.ts
similarity index 100%
rename from lib/msal-core/src/AccessTokenCacheItem.ts
rename to lib/msal-core/src/cache/AccessTokenCacheItem.ts
diff --git a/lib/msal-core/src/AccessTokenKey.ts b/lib/msal-core/src/cache/AccessTokenKey.ts
similarity index 85%
rename from lib/msal-core/src/AccessTokenKey.ts
rename to lib/msal-core/src/cache/AccessTokenKey.ts
index e94051f3a4..6f07d7f158 100644
--- a/lib/msal-core/src/AccessTokenKey.ts
+++ b/lib/msal-core/src/cache/AccessTokenKey.ts
@@ -3,8 +3,8 @@
* Licensed under the MIT License.
*/
-import { CryptoUtils } from "./utils/CryptoUtils";
-import { UrlUtils } from "./utils/UrlUtils";
+import { CryptoUtils } from "../utils/CryptoUtils";
+import { UrlUtils } from "../utils/UrlUtils";
/**
* @hidden
diff --git a/lib/msal-core/src/AccessTokenValue.ts b/lib/msal-core/src/cache/AccessTokenValue.ts
similarity index 100%
rename from lib/msal-core/src/AccessTokenValue.ts
rename to lib/msal-core/src/cache/AccessTokenValue.ts
diff --git a/lib/msal-core/src/cache/AuthCache.ts b/lib/msal-core/src/cache/AuthCache.ts
new file mode 100644
index 0000000000..7ef83152cb
--- /dev/null
+++ b/lib/msal-core/src/cache/AuthCache.ts
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { Constants, PersistentCacheKeys, TemporaryCacheKeys, RequestStatus } from "../utils/Constants";
+import { AccessTokenCacheItem } from "./AccessTokenCacheItem";
+import { CacheLocation } from "../Configuration";
+import { BrowserStorage } from "./BrowserStorage";
+
+/**
+ * @hidden
+ */
+export class AuthCache extends BrowserStorage {// Singleton
+
+ private clientId: string;
+ private rollbackEnabled: boolean;
+
+ constructor(clientId: string, cacheLocation: CacheLocation, storeAuthStateInCookie: boolean) {
+ super(cacheLocation);
+ this.clientId = clientId;
+ // This is hardcoded to true for now. We may make this configurable in the future
+ this.rollbackEnabled = true;
+ this.migrateCacheEntries(storeAuthStateInCookie);
+ }
+
+ private migrateCacheEntries(storeAuthStateInCookie: boolean) {
+ const idTokenKey = `${Constants.cachePrefix}.${PersistentCacheKeys.IDTOKEN}`;
+ const clientInfoKey = `${Constants.cachePrefix}.${PersistentCacheKeys.CLIENT_INFO}`;
+ const errorKey = `${Constants.cachePrefix}.${PersistentCacheKeys.ERROR}`;
+ const errorDescKey = `${Constants.cachePrefix}.${PersistentCacheKeys.ERROR_DESC}`;
+
+ const idTokenValue = super.getItem(idTokenKey);
+ const clientInfoValue = super.getItem(clientInfoKey);
+ const errorValue = super.getItem(errorKey);
+ const errorDescValue = super.getItem(errorDescKey);
+ const values = [idTokenValue, clientInfoValue, errorValue, errorDescValue];
+ const keysToMigrate = [PersistentCacheKeys.IDTOKEN, PersistentCacheKeys.CLIENT_INFO, PersistentCacheKeys.ERROR, PersistentCacheKeys.ERROR_DESC];
+
+ keysToMigrate.forEach((cacheKey, index) => this.duplicateCacheEntry(cacheKey, values[index], storeAuthStateInCookie));
+ }
+
+ private duplicateCacheEntry(newKey: string, value: string, storeAuthStateInCookie?: boolean) {
+ if (value) {
+ this.setItem(newKey, value, storeAuthStateInCookie);
+ }
+ }
+
+ // Prepend msal. to each key
+ private generateCacheKey(key: string, addInstanceId: boolean): string {
+ try {
+ JSON.parse(key);
+ return key;
+ } catch (e) {
+ if (key.startsWith(`${Constants.cachePrefix}`) || key.startsWith(PersistentCacheKeys.ADAL_ID_TOKEN)) {
+ return key;
+ } else {
+ return addInstanceId ? `${Constants.cachePrefix}.${this.clientId}.${key}` : `${Constants.cachePrefix}.${key}`;
+ }
+ }
+ }
+
+ // add value to storage
+ setItem(key: string, value: string, enableCookieStorage?: boolean): void {
+ super.setItem(this.generateCacheKey(key, true), value, enableCookieStorage);
+
+ if (this.rollbackEnabled) {
+ super.setItem(this.generateCacheKey(key, false), value, enableCookieStorage);
+ }
+ }
+
+ // get one item by key from storage
+ getItem(key: string, enableCookieStorage?: boolean): string {
+ return super.getItem(this.generateCacheKey(key, true), enableCookieStorage);
+ }
+
+ // remove value from storage
+ removeItem(key: string): void {
+ super.removeItem(this.generateCacheKey(key, true));
+ if (this.rollbackEnabled) {
+ super.removeItem(this.generateCacheKey(key, false));
+ }
+ }
+
+ resetCacheItems(): void {
+ const storage = window[this.cacheLocation];
+ let key: string;
+ for (key in storage) {
+ if (storage.hasOwnProperty(key)) {
+ // Check if key contains msal prefix
+ if (key.indexOf(Constants.cachePrefix) !== -1) {
+ // For now, we are clearing all cache items created by MSAL.js
+ super.removeItem(key);
+ // TODO: Clear cache based on client id (clarify use cases where this is needed)
+ }
+ }
+ }
+ }
+
+ setItemCookie(cName: string, cValue: string, expires?: number): void {
+ super.setItemCookie(this.generateCacheKey(cName, true), cValue, expires);
+ if (this.rollbackEnabled) {
+ super.setItemCookie(this.generateCacheKey(cName, false), cValue, expires);
+ }
+ }
+
+ getItemCookie(cName: string): string {
+ return super.getItemCookie(this.generateCacheKey(cName, true));
+ }
+
+ getAllAccessTokens(clientId: string, homeAccountIdentifier: string): Array {
+ const results: Array = [];
+ let accessTokenCacheItem: AccessTokenCacheItem;
+ const storage = window[this.cacheLocation];
+ if (storage) {
+ let key: string;
+ for (key in storage) {
+ if (storage.hasOwnProperty(key)) {
+ if (key.match(clientId) && key.match(homeAccountIdentifier)) {
+ const value = this.getItem(key);
+ if (value) {
+ accessTokenCacheItem = new AccessTokenCacheItem(JSON.parse(key), JSON.parse(value));
+ results.push(accessTokenCacheItem);
+ }
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+ removeAcquireTokenEntries(state: string): void {
+ const storage = window[this.cacheLocation];
+ let key: string;
+ for (key in storage) {
+ if (storage.hasOwnProperty(key)) {
+ if ((key.indexOf(TemporaryCacheKeys.AUTHORITY) !== -1 || key.indexOf(TemporaryCacheKeys.ACQUIRE_TOKEN_ACCOUNT) !== 1) && (!state || key.indexOf(state) !== -1)) {
+ const resourceDelimSplitKey = key.split(Constants.resourceDelimiter);
+ let keyState;
+ if (resourceDelimSplitKey.length > 1) {
+ keyState = resourceDelimSplitKey[resourceDelimSplitKey.length-1];
+ }
+ if (keyState === state && !this.tokenRenewalInProgress(keyState)) {
+ this.removeItem(key);
+ this.removeItem(TemporaryCacheKeys.RENEW_STATUS + state);
+ this.removeItem(TemporaryCacheKeys.STATE_LOGIN);
+ this.removeItem(TemporaryCacheKeys.STATE_ACQ_TOKEN);
+ this.removeItem(TemporaryCacheKeys.LOGIN_REQUEST);
+ this.removeItem(TemporaryCacheKeys.INTERACTION_STATUS);
+ this.removeItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${state}`);
+ this.setItemCookie(key, "", -1);
+ this.clearMsalCookie(state);
+ }
+ }
+ }
+ }
+ }
+
+ private tokenRenewalInProgress(stateValue: string): boolean {
+ const renewStatus = this.getItem(TemporaryCacheKeys.RENEW_STATUS + stateValue);
+ return !!(renewStatus && renewStatus === RequestStatus.IN_PROGRESS);
+ }
+
+ public clearMsalCookie(state?: string): void {
+ const nonceKey = state ? `${TemporaryCacheKeys.NONCE_IDTOKEN}|${state}` : TemporaryCacheKeys.NONCE_IDTOKEN;
+ this.clearItemCookie(nonceKey);
+ this.clearItemCookie(TemporaryCacheKeys.STATE_LOGIN);
+ this.clearItemCookie(TemporaryCacheKeys.LOGIN_REQUEST);
+ this.clearItemCookie(TemporaryCacheKeys.STATE_ACQ_TOKEN);
+ }
+
+ /**
+ * Create acquireTokenAccountKey to cache account object
+ * @param accountId
+ * @param state
+ */
+ public static generateAcquireTokenAccountKey(accountId: any, state: string): string {
+ return TemporaryCacheKeys.ACQUIRE_TOKEN_ACCOUNT + Constants.resourceDelimiter +
+ `${accountId}` + Constants.resourceDelimiter + `${state}`;
+ }
+
+ /**
+ * Create authorityKey to cache authority
+ * @param state
+ */
+ public static generateAuthorityKey(state: string): string {
+ return TemporaryCacheKeys.AUTHORITY + Constants.resourceDelimiter + `${state}`;
+ }
+}
diff --git a/lib/msal-core/src/cache/BrowserStorage.ts b/lib/msal-core/src/cache/BrowserStorage.ts
new file mode 100644
index 0000000000..94e744b96f
--- /dev/null
+++ b/lib/msal-core/src/cache/BrowserStorage.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { CacheLocation } from "../Configuration";
+import { ClientConfigurationError } from "../error/ClientConfigurationError";
+import { AuthError } from "../error/AuthError";
+
+/**
+ * @hidden
+ */
+export class BrowserStorage {// Singleton
+
+ protected cacheLocation: CacheLocation;
+
+ constructor(cacheLocation: CacheLocation) {
+ if (!window) {
+ throw AuthError.createNoWindowObjectError("Browser storage class could not find window object");
+ }
+
+ const storageSupported = typeof window[cacheLocation] !== "undefined" && window[cacheLocation] != null;
+ if (!storageSupported) {
+ throw ClientConfigurationError.createStorageNotSupportedError(cacheLocation);
+ }
+ this.cacheLocation = cacheLocation;
+ }
+
+ // add value to storage
+ setItem(key: string, value: string, enableCookieStorage?: boolean): void {
+ window[this.cacheLocation].setItem(key, value);
+ if (enableCookieStorage) {
+ this.setItemCookie(key, value);
+ }
+ }
+
+ // get one item by key from storage
+ getItem(key: string, enableCookieStorage?: boolean): string {
+ if (enableCookieStorage && this.getItemCookie(key)) {
+ return this.getItemCookie(key);
+ }
+ return window[this.cacheLocation].getItem(key);
+ }
+
+ // remove value from storage
+ removeItem(key: string): void {
+ return window[this.cacheLocation].removeItem(key);
+ }
+
+ // clear storage (remove all items from it)
+ clear(): void {
+ return window[this.cacheLocation].clear();
+ }
+
+ setItemCookie(cName: string, cValue: string, expires?: number): void {
+ let cookieStr = cName + "=" + cValue + ";";
+ if (expires) {
+ const expireTime = this.getCookieExpirationTime(expires);
+ cookieStr += "expires=" + expireTime + ";";
+ }
+
+ document.cookie = cookieStr;
+ }
+
+ getItemCookie(cName: string): string {
+ const name = cName + "=";
+ const ca = document.cookie.split(";");
+ for (let i = 0; i < ca.length; i++) {
+ let c = ca[i];
+ while (c.charAt(0) === " ") {
+ c = c.substring(1);
+ }
+ if (c.indexOf(name) === 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+ return "";
+ }
+
+ clearItemCookie(cName: string) {
+ this.setItemCookie(cName, "", -1);
+ }
+
+ getCookieExpirationTime(cookieLifeDays: number): string {
+ const today = new Date();
+ const expr = new Date(today.getTime() + cookieLifeDays * 24 * 60 * 60 * 1000);
+ return expr.toUTCString();
+ }
+}
diff --git a/lib/msal-core/src/error/AuthError.ts b/lib/msal-core/src/error/AuthError.ts
index 89276bb293..8931490183 100644
--- a/lib/msal-core/src/error/AuthError.ts
+++ b/lib/msal-core/src/error/AuthError.ts
@@ -7,6 +7,10 @@ export const AuthErrorMessage = {
unexpectedError: {
code: "unexpected_error",
desc: "Unexpected error in authentication."
+ },
+ noWindowObjectError: {
+ code: "no_window_object",
+ desc: "No window object available. Details:"
}
};
@@ -30,4 +34,8 @@ export class AuthError extends Error {
static createUnexpectedError(errDesc: string) {
return new AuthError(AuthErrorMessage.unexpectedError.code, `${AuthErrorMessage.unexpectedError.desc}: ${errDesc}`);
}
+
+ static createNoWindowObjectError(errDesc: string) {
+ return new AuthError(AuthErrorMessage.noWindowObjectError.code, `${AuthErrorMessage.noWindowObjectError.desc} ${errDesc}`);
+ }
}
diff --git a/lib/msal-core/src/error/ClientConfigurationError.ts b/lib/msal-core/src/error/ClientConfigurationError.ts
index de2a4036eb..46fe066e3e 100644
--- a/lib/msal-core/src/error/ClientConfigurationError.ts
+++ b/lib/msal-core/src/error/ClientConfigurationError.ts
@@ -12,13 +12,9 @@ export const ClientConfigurationErrorMessage = {
code: "no_config_set",
desc: "Configuration has not been set. Please call the UserAgentApplication constructor with a valid Configuration object."
},
- invalidCacheLocation: {
- code: "invalid_cache_location",
- desc: "The cache location provided is not valid."
- },
- noStorageSupported: {
- code: "browser_storage_not_supported",
- desc: "localStorage and sessionStorage are not supported."
+ storageNotSupported: {
+ code: "storage_not_supported",
+ desc: "The value for the cacheLocation is not supported."
},
noRedirectCallbacksSet: {
code: "no_redirect_callbacks",
@@ -100,14 +96,9 @@ export class ClientConfigurationError extends ClientAuthError {
`${ClientConfigurationErrorMessage.configurationNotSet.desc}`);
}
- static createInvalidCacheLocationConfigError(givenCacheLocation: string): ClientConfigurationError {
- return new ClientConfigurationError(ClientConfigurationErrorMessage.invalidCacheLocation.code,
- `${ClientConfigurationErrorMessage.invalidCacheLocation.desc} Provided value: ${givenCacheLocation}. Possible values are: ${Constants.cacheLocationLocal}, ${Constants.cacheLocationSession}.`);
- }
-
- static createNoStorageSupportedError() : ClientConfigurationError {
- return new ClientConfigurationError(ClientConfigurationErrorMessage.noStorageSupported.code,
- ClientConfigurationErrorMessage.noStorageSupported.desc);
+ static createStorageNotSupportedError(givenCacheLocation: string) : ClientConfigurationError {
+ return new ClientConfigurationError(ClientConfigurationErrorMessage.storageNotSupported.code,
+ `${ClientConfigurationErrorMessage.storageNotSupported.desc} Given location: ${givenCacheLocation}`);
}
static createRedirectCallbacksNotSetError(): ClientConfigurationError {
diff --git a/lib/msal-core/src/messaging/MessageCache.ts b/lib/msal-core/src/messaging/MessageCache.ts
new file mode 100644
index 0000000000..efa514a863
--- /dev/null
+++ b/lib/msal-core/src/messaging/MessageCache.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { MessageType } from "./MessageHelper";
+import { AuthCache } from "./../cache/AuthCache";
+
+/**
+ * Handles Message cache operations
+ * @hidden
+ */
+export class MessageCache {
+
+ private storage: AuthCache = null;
+
+ /**
+ * initialize the class with Storage type
+ * @param cacheStorage
+ */
+ constructor(cacheStorage: AuthCache) {
+ this.storage = cacheStorage;
+ }
+
+ /**
+ * Writes the message to the cache
+ * @param key
+ * @param data
+ */
+ write(key: MessageType, data: string) {
+ this.storage.setItem(key, data);
+ }
+
+ /**
+ * retrieves the message from the cache
+ * @param key
+ */
+ read(key: MessageType) {
+ return this.storage.getItem(key);
+ }
+
+ /**
+ * erase the message from the cache
+ * @param key
+ */
+ erase(key: MessageType) {
+ this.storage.removeItem(key);
+ }
+
+}
diff --git a/lib/msal-core/src/messaging/MessageDispatcher.ts b/lib/msal-core/src/messaging/MessageDispatcher.ts
new file mode 100644
index 0000000000..b6b1ef7ff8
--- /dev/null
+++ b/lib/msal-core/src/messaging/MessageDispatcher.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { PAYLOAD } from "./MessageHelper";
+
+export class MessageDispatcher {
+
+ static dispatchMessage(target: Window, message: PAYLOAD, originCheck?: string) {
+ originCheck ? target.postMessage(message, originCheck): target.postMessage(message, "*");
+ }
+}
diff --git a/lib/msal-core/src/messaging/MessageHelper.ts b/lib/msal-core/src/messaging/MessageHelper.ts
new file mode 100644
index 0000000000..d39c62a4ba
--- /dev/null
+++ b/lib/msal-core/src/messaging/MessageHelper.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { WindowUtils } from "./../utils/WindowUtils";
+import { MessageCache } from "./MessageCache";
+import { Logger } from "./../Logger";
+import { MessageDispatcher } from "./MessageDispatcher";
+
+// type of the message
+export enum MessageType {
+ REDIRECT_REQUEST = "REDIRECT_REQUEST",
+ HASH = "HASH",
+ URL_TOP_FRAME = "URL_TOP_FRAME",
+ URL_NAVIGATE = "URL_NAVIGATE"
+};
+
+// helps differentiate topframe and iframe context
+export enum WindowType {
+ TOP_FRAME,
+ IFRAME
+};
+
+// message payload
+export type PAYLOAD = {
+ type: MessageType,
+ data?: string
+};
+
+export class MessageHelper {
+
+ /**
+ * returns the current window type: Top Frame app or Iframed app
+ */
+ static currentWindow(): WindowType {
+ if(WindowUtils.isWindowOnTop()) {
+ return WindowType.TOP_FRAME;
+ }
+ else if(WindowUtils.isInIframe()) {
+ return WindowType.IFRAME;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Builds a message in the format: MESSAGE_SCHEMA
+ *
+ * @param messageType
+ * @param contentType
+ * @param messageData
+ */
+ static buildMessage(messageType: MessageType, messageData?: string): PAYLOAD {
+ const message: PAYLOAD = {
+ type: messageType,
+ data: messageData
+ };
+
+ return message;
+ }
+
+ /**
+ * utility to handle redirect(302) from the service on behalf of the iframed application
+ *
+ * @param messageCache
+ * @param urlTopFrame
+ * @param urlHash
+ * @param logger
+ */
+ static handleTopFrameRedirect(messageCache: MessageCache, urlTopFrame: string, urlHash: string, logger: Logger) {
+ // write the hash to the cache of the redirect URI, clear the cache(and hence the state) for the Top Frame delegation indication
+ messageCache.write(MessageType.HASH, urlHash);
+ messageCache.erase(MessageType.URL_TOP_FRAME);
+
+ // navigate to the saved URL
+ WindowUtils.navigateWindow(urlTopFrame, logger);
+ }
+
+ /**
+ * Handle the redirect delegation at the topframe on behalf of the embedded (iframed) application
+ *
+ * @param messageCache
+ * @param urlNavigate
+ */
+ static redirectDelegationRequest(messageCache: MessageCache, urlNavigate: string, topFrameOrigin: string) {
+ // save the URL to navigate in the cache and send a request to the topframe
+ messageCache.write(MessageType.URL_NAVIGATE, urlNavigate);
+
+ // dispatch the message to the top window to start redirect flow by delegation
+ const message = MessageHelper.buildMessage(MessageType.REDIRECT_REQUEST);
+ const targetWindow = window.top;
+ MessageDispatcher.dispatchMessage(targetWindow, message, topFrameOrigin);
+ }
+}
diff --git a/lib/msal-core/src/messaging/MessageListener.ts b/lib/msal-core/src/messaging/MessageListener.ts
new file mode 100644
index 0000000000..f55756e660
--- /dev/null
+++ b/lib/msal-core/src/messaging/MessageListener.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { WindowType, MessageHelper } from "./MessageHelper";
+import { WindowUtils } from "./../utils/WindowUtils";
+import { MessageType, PAYLOAD } from "./MessageHelper";
+import { MessageCache } from "./MessageCache";
+import { MessageDispatcher } from "./MessageDispatcher";
+
+import { Logger } from "./../Logger";
+
+export class MessageListener {
+
+ private messageCache: MessageCache;
+ private logger: Logger;
+ private topFrameOrigin: string;
+ private embeddedFrameOrigin: string;
+
+ /**
+ * initialize the message listener, and register the callback to process messages
+ */
+ constructor(messageCache: MessageCache, logger: Logger, topFrameOrigin?: string, embeddedFrameOrigin?: string) {
+ this.messageCache = messageCache;
+ this.logger = logger;
+ this.topFrameOrigin = topFrameOrigin;
+ this.embeddedFrameOrigin = embeddedFrameOrigin;
+
+ // listen to all incoming messages
+ window.addEventListener("message", this.receiveMessage, false);
+ }
+
+ /**
+ * Parses the messages receieved
+ * This will be a unique handler per message, we will allow only one active request at a time
+ * @param event
+ */
+ private receiveMessage(event: any) {
+
+ const windowType = MessageHelper.currentWindow();
+ const receivedMessage: PAYLOAD = { ...event.data};
+
+ switch(windowType) {
+
+ // Top framed application: handles the delegation on behalf of the iframed app
+ case WindowType.TOP_FRAME: {
+
+ switch(receivedMessage.type) {
+ case MessageType.REDIRECT_REQUEST: {
+ // acknowlege the redirect on behalf of the iframed app by sending the current location
+ const message = MessageHelper.buildMessage(MessageType.URL_TOP_FRAME, window.location.href);
+ MessageDispatcher.dispatchMessage(event.source, message, this.embeddedFrameOrigin);
+ break;
+ }
+
+ case MessageType.URL_NAVIGATE: {
+ // if the response is the URL to navigate for token acquisition, navigate to STS
+ if (receivedMessage.type === MessageType.URL_NAVIGATE) {
+ this.logger.info("navigating to the Service on behalf of the iframed app");
+ WindowUtils.navigateWindow(receivedMessage.data, this.logger);
+ }
+ break;
+ }
+ }
+ }
+
+ // embedded (in an iframe) application
+ case WindowType.IFRAME: {
+
+ // check the origin, should match window.top always; message channel may be more secure
+ if (window.top != event.source) {
+ this.logger.warning("The message origin is not verified");
+ return;
+ }
+
+ if (receivedMessage.type === MessageType.URL_TOP_FRAME) {
+ // record the ack from the top frame - store the URL
+ this.messageCache.write(MessageType.URL_TOP_FRAME, receivedMessage.data);
+
+ // respond with the URL to navigate for token acquisition
+ const urlNavigate = this.messageCache.read(MessageType.URL_NAVIGATE);
+ const message = MessageHelper.buildMessage(MessageType.URL_NAVIGATE, urlNavigate);
+ MessageDispatcher.dispatchMessage(event.source, message, this.topFrameOrigin);
+ }
+
+ break;
+ }
+ }
+ }
+}
diff --git a/lib/msal-core/src/utils/Constants.ts b/lib/msal-core/src/utils/Constants.ts
index c043ff818f..9dcbf953d5 100644
--- a/lib/msal-core/src/utils/Constants.ts
+++ b/lib/msal-core/src/utils/Constants.ts
@@ -10,40 +10,11 @@ import { CacheLocation } from "../Configuration";
* Constants
*/
export class Constants {
- static get errorDescription(): string { return "error_description"; }
- static get error(): string { return "error"; }
-
- static get scope(): string { return "scope"; }
- static get clientInfo(): string { return "client_info"; }
+ static get claims(): string { return "claims"; }
static get clientId(): string { return "clientId"; }
- static get idToken(): string { return "id_token"; }
static get adalIdToken(): string { return "adal.idtoken"; }
- static get accessToken(): string { return "access_token"; }
- static get expiresIn(): string { return "expires_in"; }
- static get sessionState(): string { return "session_state"; }
- static get claims(): string { return "claims"; }
-
- static get msalClientInfo(): string { return "msal.client.info"; }
- static get msalError(): string { return "msal.error"; }
- static get msalErrorDescription(): string { return "msal.error.description"; }
-
- static get msalSessionState(): string { return "msal.session.state"; }
- static get tokenKeys(): string { return "msal.token.keys"; }
- static get accessTokenKey(): string { return "msal.access.token.key"; }
- static get expirationKey(): string { return "msal.expiration.key"; }
- static get stateLogin(): string { return "msal.state.login"; }
- static get stateAcquireToken(): string { return "msal.state.acquireToken"; }
- static get stateRenew(): string { return "msal.state.renew"; }
- static get nonceIdToken(): string { return "msal.nonce.idtoken"; }
- static get userName(): string { return "msal.username"; }
- static get idTokenKey(): string { return "msal.idtoken"; }
- static get loginRequest(): string { return "msal.login.request"; }
- static get loginError(): string { return "msal.login.error"; }
- static get renewStatus(): string { return "msal.token.renew.status"; }
- static get urlHash(): string { return "msal.urlHash"; }
- static get angularLoginRequest(): string { return "msal.angular.login.request"; }
- static get msal(): string { return "msal"; }
+ static get cachePrefix(): string { return "msal"; }
static get no_account(): string { return "NO_ACCOUNT"; }
static get consumersUtid(): string { return "9188040d-6c67-4c5b-b112-36a304b66dad"; }
@@ -55,10 +26,7 @@ export class Constants {
static get response_mode_fragment(): string { return "&response_mode=fragment"; }
static get resourceDelimiter(): string { return "|"; }
-
- static get tokenRenewStatusCancelled(): string { return "Canceled"; }
- static get tokenRenewStatusCompleted(): string { return "Completed"; }
- static get tokenRenewStatusInProgress(): string { return "In Progress"; }
+ static get cacheDelimiter(): string { return "."; }
private static _popUpWidth: number = 483;
static get popUpWidth(): number { return this._popUpWidth; }
@@ -81,22 +49,61 @@ export class Constants {
static get openidScope(): string { return "openid"; }
static get profileScope(): string { return "profile"; }
- static get cacheLocationLocal(): CacheLocation { return "localStorage"; }
- static get cacheLocationSession(): CacheLocation { return "sessionStorage"; }
-
static get interactionTypeRedirect(): InteractionType { return "redirectInteraction"; }
static get interactionTypePopup(): InteractionType { return "popupInteraction"; }
}
/**
- * @hidden
- * CacheKeys for 'authority' and 'account'
+ * Status of the current token request
+ */
+export enum RequestStatus {
+ CANCELLED = "Cancelled",
+ COMPLETED = "Completed",
+ IN_PROGRESS = "InProgress"
+};
+
+/**
+ * Keys in the hashParams
*/
-export const CacheKeys = {
- AUTHORITY: "msal.authority",
- ACQUIRE_TOKEN_ACCOUNT: "msal.acquireTokenAccount"
+export enum ServerHashParamKeys {
+ SCOPE = "scope",
+ ERROR = "error",
+ ERROR_DESCRIPTION = "error_description",
+ ACCESS_TOKEN = "access_token",
+ ID_TOKEN = "id_token",
+ EXPIRES_IN = "expires_in",
+ SESSION_STATE = "session_state",
+ CLIENT_INFO = "client_info"
};
+/**
+ * @hidden
+ * CacheKeys for MSAL
+ */
+export enum TemporaryCacheKeys {
+ AUTHORITY = "authority",
+ ACQUIRE_TOKEN_ACCOUNT = "acquireTokenAccount",
+ SESSION_STATE = "session.state",
+ STATE_LOGIN = "state.login",
+ STATE_ACQ_TOKEN = "state.acquireToken",
+ STATE_RENEW = "state.renew",
+ NONCE_IDTOKEN = "nonce.idtoken",
+ LOGIN_REQUEST = "login.request",
+ RENEW_STATUS = "token.renew.status",
+ URL_HASH = "urlHash",
+ ANGULAR_LOGIN_REQUEST = "angular.login.request",
+ INTERACTION_STATUS = "interaction.status"
+}
+
+export enum PersistentCacheKeys {
+ IDTOKEN = "idtoken",
+ CLIENT_INFO = "client.info",
+ ADAL_ID_TOKEN = "adal.idtoken",
+ ERROR = "error",
+ ERROR_DESC = "error.description",
+ LOGIN_ERROR = "login.error"
+}
+
export const AADTrustedHostList = {
"login.windows.net": "login.windows.net",
"login.chinacloudapi.cn": "login.chinacloudapi.cn",
@@ -110,18 +117,18 @@ export const AADTrustedHostList = {
* @hidden
* SSO Types - generated to populate hints
*/
-export const SSOTypes = {
- ACCOUNT: "account",
- SID: "sid",
- LOGIN_HINT: "login_hint",
- ID_TOKEN: "id_token",
- DOMAIN_HINT: "domain_hint",
- ORGANIZATIONS: "organizations",
- CONSUMERS: "consumers",
- ACCOUNT_ID: "accountIdentifier",
- HOMEACCOUNT_ID: "homeAccountIdentifier",
- LOGIN_REQ: "login_req",
- DOMAIN_REQ: "domain_req"
+export enum SSOTypes {
+ ACCOUNT = "account",
+ SID = "sid",
+ LOGIN_HINT = "login_hint",
+ ID_TOKEN ="id_token",
+ DOMAIN_HINT = "domain_hint",
+ ORGANIZATIONS = "organizations",
+ CONSUMERS = "consumers",
+ ACCOUNT_ID = "accountIdentifier",
+ HOMEACCOUNT_ID = "homeAccountIdentifier",
+ LOGIN_REQ = "login_req",
+ DOMAIN_REQ = "domain_req"
};
/**
diff --git a/lib/msal-core/src/utils/UrlUtils.ts b/lib/msal-core/src/utils/UrlUtils.ts
index 268e4f4d3c..9a06d23419 100644
--- a/lib/msal-core/src/utils/UrlUtils.ts
+++ b/lib/msal-core/src/utils/UrlUtils.ts
@@ -4,7 +4,7 @@
*/
import { IUri } from "../IUri";
-import { Constants, SSOTypes } from "./Constants";
+import { Constants, SSOTypes, ServerHashParamKeys } from "./Constants";
import { ServerRequestParameters } from "../ServerRequestParameters";
import { ScopeSet } from "../ScopeSet";
import { StringUtils } from "./StringUtils";
@@ -97,7 +97,7 @@ export class UrlUtils {
/**
* Returns current window URL as redirect uri
*/
- static getDefaultRedirectUri(): string {
+ static getCurrentUri(): string {
return window.location.href.split("?")[0].split("#")[0];
}
@@ -228,10 +228,10 @@ export class UrlUtils {
static urlContainsHash(urlString: string): boolean {
const parameters = UrlUtils.deserializeHash(urlString);
return (
- parameters.hasOwnProperty(Constants.errorDescription) ||
- parameters.hasOwnProperty(Constants.error) ||
- parameters.hasOwnProperty(Constants.accessToken) ||
- parameters.hasOwnProperty(Constants.idToken)
+ parameters.hasOwnProperty(ServerHashParamKeys.ERROR) ||
+ parameters.hasOwnProperty(ServerHashParamKeys.ERROR_DESCRIPTION) ||
+ parameters.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN) ||
+ parameters.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)
);
}
diff --git a/lib/msal-core/src/utils/WindowUtils.ts b/lib/msal-core/src/utils/WindowUtils.ts
index 179880038b..f994ce5fa0 100644
--- a/lib/msal-core/src/utils/WindowUtils.ts
+++ b/lib/msal-core/src/utils/WindowUtils.ts
@@ -1,6 +1,8 @@
import { ClientAuthError } from "../error/ClientAuthError";
import { UrlUtils } from "./UrlUtils";
+import { StringUtils } from "./StringUtils";
import { Logger } from "../Logger";
+import { AuthError } from "./../error/AuthError";
export class WindowUtils {
/**
@@ -19,6 +21,15 @@ export class WindowUtils {
return window.parent !== window;
}
+ /**
+ * @hidden
+ * Checks if the current page is running in an iframe.
+ * @ignore
+ */
+ static isWindowOnTop(): boolean {
+ return window.top === window;
+ }
+
/**
* @hidden
* Check if the current page is running in a popup.
@@ -201,4 +212,23 @@ export class WindowUtils {
WindowUtils.getPopups().forEach(popup => popup.close());
}
+ /**
+ * @hidden
+ * Used to redirect the browser to the STS authorization endpoint
+ * @param {string} urlNavigate - URL of the authorization endpoint
+ */
+ static navigateWindow(urlNavigate: string, logger: Logger, popupWindow?: Window) {
+ // Navigate if valid URL
+ if (urlNavigate && !StringUtils.isEmpty(urlNavigate)) {
+ const navigateWindow: Window = popupWindow ? popupWindow : window;
+ const logMessage: string = popupWindow ? "Navigated Popup window to:" + urlNavigate : "Navigate to:" + urlNavigate;
+ logger.infoPii(logMessage);
+ navigateWindow.location.replace(urlNavigate);
+ }
+ else {
+ logger.info("Navigate url is empty");
+ throw AuthError.createUnexpectedError("Navigate url is empty");
+ }
+ }
+
}
diff --git a/lib/msal-core/test/Storage.localStorage.spec.ts b/lib/msal-core/test/Storage.localStorage.spec.ts
index 862cccd390..fbb016a0ad 100644
--- a/lib/msal-core/test/Storage.localStorage.spec.ts
+++ b/lib/msal-core/test/Storage.localStorage.spec.ts
@@ -1,19 +1,23 @@
import { expect } from "chai";
import sinon from "sinon";
-import { Storage } from "../src/Storage";
-import { Constants } from "../src";
-import { CacheKeys } from "../src/utils/Constants";
-import { AccessTokenKey } from "../src/AccessTokenKey";
-import { AccessTokenValue } from "../src/AccessTokenValue";
+import { BrowserStorage } from "../src/cache/BrowserStorage";
+import { AuthCache } from "../src/cache/AuthCache";
+import { Constants, AuthError } from "../src";
+import { PersistentCacheKeys, TemporaryCacheKeys } from "../src/utils/Constants";
+import { AccessTokenKey } from "../src/cache/AccessTokenKey";
+import { AccessTokenValue } from "../src/cache/AccessTokenValue";
import { Account } from "../src/Account";
+import { AuthErrorMessage } from "../src/error/AuthError";
+import { ClientConfigurationErrorMessage, ClientConfigurationError } from "../src/error/ClientConfigurationError";
describe("CacheStorage.ts Class - Local Storage", function () {
- let TEST_KEY = "test_key";
- let TEST_VALUE = "test value";
- let TEST_ACCOUNT_ID = "1234";
- let TEST_STATE = "state5678";
- let TEST_STATE2 = "state9012";
- let cacheStorage : Storage;
+ const TEST_KEY = "test_key";
+ const TEST_VALUE = "test value";
+ const TEST_ACCOUNT_ID = "1234";
+ const TEST_STATE = "state5678";
+ const TEST_STATE2 = "state9012";
+ const LOCAL_STORAGE = "localStorage";
+ let cacheStorage : BrowserStorage;
let ACCESS_TOKEN_KEY : AccessTokenKey;
let ACCESS_TOKEN_VALUE : AccessTokenValue;
let ACCOUNT : Account;
@@ -58,29 +62,58 @@ describe("CacheStorage.ts Class - Local Storage", function () {
});
it("parses the cache location correctly", function () {
- cacheStorage = new Storage("localStorage");
+ cacheStorage = new BrowserStorage(LOCAL_STORAGE);
cacheStorage.setItem(TEST_KEY, TEST_VALUE);
expect(window.localStorage.getItem(TEST_KEY)).to.be.eq(TEST_VALUE);
});
it("throws error if cache location is not supported", function () {
- // Cannot test with current tooling - will need to take a look
- // Possibly wrapple as an option here? https://github.com/mroderick/wrapple
- });
-
- it("uses previous storage instance if one already exists", function () {
- let oldCacheStorage = new Storage(Constants.cacheLocationLocal);
- cacheStorage = new Storage(Constants.cacheLocationSession);
- expect(cacheStorage).to.deep.eq(oldCacheStorage);
+ sinon.stub(window, LOCAL_STORAGE).value(null);
+ console.log(window.localStorage);
+ let authErr;
+ try {
+ cacheStorage = new BrowserStorage(LOCAL_STORAGE);
+ } catch (e) {
+ authErr = e;
+ }
+ expect(authErr instanceof ClientConfigurationError).to.be.true;
+ expect(authErr instanceof Error).to.be.true;
+ expect(authErr.errorCode).to.equal(ClientConfigurationErrorMessage.storageNotSupported.code);
+ expect(authErr.errorMessage).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(authErr.message).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(authErr.errorMessage).to.include(LOCAL_STORAGE);
+ expect(authErr.message).to.include(LOCAL_STORAGE);
+ expect(authErr.name).to.equal("ClientConfigurationError");
+ expect(authErr.stack).to.include("Storage.localStorage.spec.ts");
+ sinon.restore();
});
+ it("throws error if window object does not exist", function () {
+ let authErr;
+ const oldWindow = window;
+ window = null;
+ try {
+ cacheStorage = new BrowserStorage(LOCAL_STORAGE);
+ } catch (e) {
+ authErr = e;
+ }
+ expect(authErr instanceof AuthError).to.be.true;
+ expect(authErr instanceof Error).to.be.true;
+ expect(authErr.errorCode).to.equal(AuthErrorMessage.noWindowObjectError.code);
+ expect(authErr.errorMessage).to.include(AuthErrorMessage.noWindowObjectError.desc);
+ expect(authErr.message).to.include(AuthErrorMessage.noWindowObjectError.desc);
+ expect(authErr.name).to.equal("AuthError");
+ expect(authErr.stack).to.include("Storage.localStorage.spec.ts");
+ window = oldWindow;
+ })
});
describe("localStorage access functions", function () {
beforeEach(function () {
- cacheStorage = new Storage("localStorage");
+ cacheStorage = new BrowserStorage(LOCAL_STORAGE);
setTestCacheItems();
+ document.cookie = "";
});
afterEach(function () {
@@ -114,16 +147,18 @@ describe("CacheStorage.ts Class - Local Storage", function () {
it("tests setItemCookie works", function () {
let idTokenNonceString = "idTokenNonce";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- expect(document.cookie).to.include(Constants.nonceIdToken);
+ cacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ expect(document.cookie).to.include(TemporaryCacheKeys.NONCE_IDTOKEN);
expect(document.cookie).to.include(idTokenNonceString);
+ cacheStorage.clearItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
});
it("tests getItemCookie ", function () {
let idTokenNonceString = "idTokenNonce";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- let retrievedItem = cacheStorage.getItemCookie(Constants.nonceIdToken);
+ cacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ let retrievedItem = cacheStorage.getItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
expect(retrievedItem).to.include(idTokenNonceString);
+ cacheStorage.clearItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
});
it("tests getCookieExpirationTime", function () {
@@ -136,30 +171,20 @@ describe("CacheStorage.ts Class - Local Storage", function () {
expect(actualNextDayUTC).to.be.eq(nextDayUTC.toUTCString());
expect(actualDayAfterUTC).to.be.eq(dayAfterUTC.toUTCString());
});
-
- it("tests clearCookie", function () {
- let idTokenNonceString = "idTokenNonce";
- let stateLoginString = "stateLogin";
- let loginRequestString = "loginRequest";
- let stateAcquireTokenString = "stateAcquireToken";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- cacheStorage.setItemCookie(Constants.stateLogin, stateLoginString);
- cacheStorage.setItemCookie(Constants.loginRequest, loginRequestString);
- cacheStorage.setItemCookie(Constants.stateAcquireToken, stateAcquireTokenString);
- cacheStorage.clearCookie();
- expect(document.cookie).to.be.empty;
- });
});
describe("MSAL Cache Item Management", function () {
+
+ let msalCacheStorage: AuthCache;
beforeEach(function () {
- cacheStorage = new Storage("localStorage");
+ msalCacheStorage = new AuthCache(MSAL_CLIENT_ID,LOCAL_STORAGE, true);
setTestCacheItems();
+ document.cookie = "";
});
afterEach(function () {
- cacheStorage.clear();
+ msalCacheStorage.clear();
});
it("getAllAccessTokens returns all accessTokens in cache", function () {
@@ -181,104 +206,122 @@ describe("CacheStorage.ts Class - Local Storage", function () {
at4.accessToken = "accessToken4";
window.localStorage.setItem(JSON.stringify(ACCESS_TOKEN_KEY), JSON.stringify(at4));
- let res1 = cacheStorage.getAllAccessTokens(MSAL_CLIENT_ID, "1234");
- let res2 = cacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "1234");
- let res3 = cacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "4567");
+ let res1 = msalCacheStorage.getAllAccessTokens(MSAL_CLIENT_ID, "1234");
+ let res2 = msalCacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "1234");
+ let res3 = msalCacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "4567");
expect(res1).to.be.length(1);
expect(res2).to.be.length(1);
expect(res3).to.be.length(2);
- expect(JSON.stringify(res1[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res1[0].key)));
- expect(JSON.stringify(res2[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res2[0].key)));
- expect(JSON.stringify(res3[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res3[0].key)));
- expect(JSON.stringify(res3[1].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res3[1].key)));
+ expect(JSON.stringify(res1[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res1[0].key)));
+ expect(JSON.stringify(res2[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res2[0].key)));
+ expect(JSON.stringify(res3[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res3[0].key)));
+ expect(JSON.stringify(res3[1].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res3[1].key)));
});
it("removeAcquireTokenEntries removes any acquireToken or authorityKey entries in the cache", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
- window.localStorage.setItem(acquireTokenAccountKey, JSON.stringify(ACCOUNT));
- window.localStorage.setItem(authorityKey, validAuthority);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
+
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey}`, JSON.stringify(ACCOUNT));
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey}`, validAuthority);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
- cacheStorage.removeAcquireTokenEntries();
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
- expect(cacheStorage.getItem(authorityKey)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.null;
});
it("removeAcquireTokenEntries removes specific acquireToken or authorityKey entries in the cache", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
-
- let acquireTokenAccountKey2 = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE2);
- let authorityKey2 = Storage.generateAuthorityKey(TEST_STATE2);
- window.localStorage.setItem(acquireTokenAccountKey, JSON.stringify(ACCOUNT));
- window.localStorage.setItem(authorityKey, validAuthority);
- window.localStorage.setItem(acquireTokenAccountKey2, JSON.stringify(ACCOUNT));
- window.localStorage.setItem(authorityKey2, validAuthority);
-
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
-
- cacheStorage.removeAcquireTokenEntries(TEST_STATE);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
+
+ let acquireTokenAccountKey2 = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE2);
+ let authorityKey2 = AuthCache.generateAuthorityKey(TEST_STATE2);
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey}`, JSON.stringify(ACCOUNT));
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey}`, validAuthority);
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey2}`, JSON.stringify(ACCOUNT));
+ window.localStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey2}`, validAuthority);
+
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
+
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
- expect(cacheStorage.getItem(authorityKey)).to.be.null;
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
- cacheStorage.removeAcquireTokenEntries(TEST_STATE2);
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE2);
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.null;
- expect(cacheStorage.getItem(authorityKey2)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.null;
+ });
+
+ it("tests clearCookie", function () {
+ let idTokenNonceString = "idTokenNonce";
+ let stateLoginString = "stateLogin";
+ let loginRequestString = "loginRequest";
+ let stateAcquireTokenString = "stateAcquireToken";
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.STATE_LOGIN, stateLoginString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.LOGIN_REQUEST, loginRequestString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.STATE_ACQ_TOKEN, stateAcquireTokenString);
+ msalCacheStorage.clearMsalCookie();
+ expect(document.cookie).to.be.empty;
});
- it("resetCacheItems deletes all msal related cache items", function () {
- window.localStorage.setItem(Constants.msalClientInfo, "clientInfo");
- window.localStorage.setItem(Constants.tokenKeys, "tokenKeys");
- window.localStorage.setItem(Constants.stateLogin, "stateLogin");
- window.localStorage.setItem(Constants.idTokenKey, "idToken1");
- window.localStorage.setItem(Constants.nonceIdToken, "idTokenNonce");
- window.localStorage.setItem(Constants.renewStatus + "|RANDOM_GUID", "Completed");
-
- expect(cacheStorage.getItem(Constants.msalClientInfo)).to.be.eq("clientInfo");
- expect(cacheStorage.getItem(Constants.tokenKeys)).to.be.eq("tokenKeys");
- expect(cacheStorage.getItem(Constants.stateLogin)).to.be.eq("stateLogin");
- expect(cacheStorage.getItem(Constants.idTokenKey)).to.be.eq("idToken1");
- expect(cacheStorage.getItem(Constants.nonceIdToken)).to.be.eq("idTokenNonce");
- expect(cacheStorage.getItem(Constants.renewStatus + "|RANDOM_GUID")).to.be.eq("Completed");
-
- cacheStorage.resetCacheItems();
-
- expect(cacheStorage.getItem(Constants.msalClientInfo)).to.be.null;
- expect(cacheStorage.getItem(Constants.tokenKeys)).to.be.null;
- expect(cacheStorage.getItem(Constants.idTokenKey)).to.be.null;
- expect(cacheStorage.getItem(Constants.nonceIdToken)).to.be.null;
- expect(cacheStorage.getItem(Constants.renewStatus)).to.be.null;
- expect(cacheStorage.getItem(Constants.stateLogin)).to.be.null;
+ it("resetCacheItems deletes msal related cache items", function () {
+ let clientInfoKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${PersistentCacheKeys.CLIENT_INFO}`;
+ let stateLoginKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.STATE_LOGIN}`;
+ let idTokenKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${PersistentCacheKeys.IDTOKEN}`;
+ let nonceIdTokenKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}`;
+ let renewStatusKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.RENEW_STATUS}` + "|RANDOM_GUID";
+
+ window.localStorage.setItem(clientInfoKey, "clientInfo");
+ window.localStorage.setItem(stateLoginKey, "stateLogin");
+ window.localStorage.setItem(idTokenKey, "idToken1");
+ window.localStorage.setItem(nonceIdTokenKey, "idTokenNonce");
+ window.localStorage.setItem(renewStatusKey, "Completed");
+
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO)).to.be.eq("clientInfo");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN)).to.be.eq("stateLogin");
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.be.eq("idToken1");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.NONCE_IDTOKEN)).to.be.eq("idTokenNonce");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.RENEW_STATUS + "|RANDOM_GUID")).to.be.eq("Completed");
+
+ msalCacheStorage.resetCacheItems();
+
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN)).to.be.null;
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.NONCE_IDTOKEN)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.RENEW_STATUS + "|RANDOM_GUID")).to.be.null;
});
+ it.skip("tests that resetCacheItems only deletes instance-specific cache items");
});
describe("static key generators", function () {
it("generates acquireToken account key", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
expect(acquireTokenAccountKey).to.include(TEST_ACCOUNT_ID);
expect(acquireTokenAccountKey).to.include(TEST_STATE);
- expect(acquireTokenAccountKey).to.include(CacheKeys.ACQUIRE_TOKEN_ACCOUNT);
+ expect(acquireTokenAccountKey).to.include(TemporaryCacheKeys.ACQUIRE_TOKEN_ACCOUNT);
});
it("generates authority key", function () {
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
expect(authorityKey).to.include(TEST_STATE);
- expect(authorityKey).to.include(CacheKeys.AUTHORITY);
+ expect(authorityKey).to.include(TemporaryCacheKeys.AUTHORITY);
});
});
diff --git a/lib/msal-core/test/Storage.sessionStorage.spec.ts b/lib/msal-core/test/Storage.sessionStorage.spec.ts
index c416ebc78a..0a0216468c 100644
--- a/lib/msal-core/test/Storage.sessionStorage.spec.ts
+++ b/lib/msal-core/test/Storage.sessionStorage.spec.ts
@@ -1,19 +1,23 @@
import { expect } from "chai";
import sinon from "sinon";
-import { Storage } from "../src/Storage";
-import { Constants } from "../src";
-import { CacheKeys } from "../src/utils/Constants";
-import { AccessTokenKey } from "../src/AccessTokenKey";
-import { AccessTokenValue } from "../src/AccessTokenValue";
+import { BrowserStorage } from "../src/cache/BrowserStorage";
+import { AuthCache } from "../src/cache/AuthCache";
+import { Constants, AuthError } from "../src";
+import { PersistentCacheKeys, TemporaryCacheKeys } from "../src/utils/Constants";
+import { AccessTokenKey } from "../src/cache/AccessTokenKey";
+import { AccessTokenValue } from "../src/cache/AccessTokenValue";
import { Account } from "../src/Account";
+import { AuthErrorMessage } from "../src/error/AuthError";
+import { ClientConfigurationErrorMessage, ClientConfigurationError } from "../src/error/ClientConfigurationError";
describe("CacheStorage.ts Class - Session Storage", function () {
- let TEST_KEY = "test_key";
- let TEST_VALUE = "test value";
- let TEST_ACCOUNT_ID = "1234";
- let TEST_STATE = "state5678";
- let TEST_STATE2 = "state9012";
- let cacheStorage : Storage;
+ const TEST_KEY = "test_key";
+ const TEST_VALUE = "test value";
+ const TEST_ACCOUNT_ID = "1234";
+ const TEST_STATE = "state5678";
+ const TEST_STATE2 = "state9012";
+ const SESSION_STORAGE = "sessionStorage";
+ let cacheStorage : BrowserStorage;
let ACCESS_TOKEN_KEY : AccessTokenKey;
let ACCESS_TOKEN_VALUE : AccessTokenValue;
let ACCOUNT : Account;
@@ -55,35 +59,60 @@ describe("CacheStorage.ts Class - Session Storage", function () {
afterEach(function () {
cacheStorage = null;
- sinon.restore();
});
it("parses the cache location correctly", function () {
- cacheStorage = new Storage("sessionStorage");
- sinon.stub(cacheStorage, "cacheLocation").value("sessionStorage");
+ cacheStorage = new BrowserStorage(SESSION_STORAGE);
cacheStorage.setItem(TEST_KEY, TEST_VALUE);
expect(window.sessionStorage.getItem(TEST_KEY)).to.be.eq(TEST_VALUE);
});
it("throws error if cache location is not supported", function () {
- // Cannot test with current tooling - will need to take a look
- // Possibly wrapple as an option here? https://github.com/mroderick/wrapple
- });
-
- it("uses previous storage instance if one already exists", function () {
- let oldCacheStorage = new Storage(Constants.cacheLocationSession);
- cacheStorage = new Storage(Constants.cacheLocationLocal);
- expect(cacheStorage).to.deep.eq(oldCacheStorage);
+ sinon.stub(window, SESSION_STORAGE).value(null);
+ let authErr;
+ try {
+ cacheStorage = new BrowserStorage(SESSION_STORAGE);
+ } catch (e) {
+ authErr = e;
+ }
+ expect(authErr instanceof ClientConfigurationError).to.be.true;
+ expect(authErr instanceof Error).to.be.true;
+ expect(authErr.errorCode).to.equal(ClientConfigurationErrorMessage.storageNotSupported.code);
+ expect(authErr.errorMessage).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(authErr.message).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(authErr.errorMessage).to.include(SESSION_STORAGE);
+ expect(authErr.message).to.include(SESSION_STORAGE);
+ expect(authErr.name).to.equal("ClientConfigurationError");
+ expect(authErr.stack).to.include("Storage.sessionStorage.spec.ts");
+ sinon.restore();
});
+ it("throws error if window object does not exist", function () {
+ let authErr;
+ const oldWindow = window;
+ window = null;
+ try {
+ cacheStorage = new BrowserStorage(SESSION_STORAGE);
+ } catch (e) {
+ authErr = e;
+ }
+ expect(authErr instanceof AuthError).to.be.true;
+ expect(authErr instanceof Error).to.be.true;
+ expect(authErr.errorCode).to.equal(AuthErrorMessage.noWindowObjectError.code);
+ expect(authErr.errorMessage).to.include(AuthErrorMessage.noWindowObjectError.desc);
+ expect(authErr.message).to.include(AuthErrorMessage.noWindowObjectError.desc);
+ expect(authErr.name).to.equal("AuthError");
+ expect(authErr.stack).to.include("Storage.sessionStorage.spec.ts");
+ window = oldWindow;
+ })
});
describe("sessionStorage access functions", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
- sinon.stub(cacheStorage, "cacheLocation").value("sessionStorage");
+ cacheStorage = new BrowserStorage(SESSION_STORAGE);
setTestCacheItems();
+ document.cookie = "";
});
afterEach(function () {
@@ -117,16 +146,18 @@ describe("CacheStorage.ts Class - Session Storage", function () {
it("tests setItemCookie works", function () {
let idTokenNonceString = "idTokenNonce";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- expect(document.cookie).to.include(Constants.nonceIdToken);
+ cacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ expect(document.cookie).to.include(TemporaryCacheKeys.NONCE_IDTOKEN);
expect(document.cookie).to.include(idTokenNonceString);
+ cacheStorage.clearItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
});
it("tests getItemCookie ", function () {
let idTokenNonceString = "idTokenNonce";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- let retrievedItem = cacheStorage.getItemCookie(Constants.nonceIdToken);
+ cacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ let retrievedItem = cacheStorage.getItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
expect(retrievedItem).to.include(idTokenNonceString);
+ cacheStorage.clearItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN);
});
it("tests getCookieExpirationTime", function () {
@@ -139,32 +170,20 @@ describe("CacheStorage.ts Class - Session Storage", function () {
expect(actualNextDayUTC).to.be.eq(nextDayUTC.toUTCString());
expect(actualDayAfterUTC).to.be.eq(dayAfterUTC.toUTCString());
});
-
- it("tests clearCookie", function () {
- let idTokenNonceString = "idTokenNonce";
- let stateLoginString = "stateLogin";
- let loginRequestString = "loginRequest";
- let stateAcquireTokenString = "stateAcquireToken";
- cacheStorage.setItemCookie(Constants.nonceIdToken, idTokenNonceString);
- cacheStorage.setItemCookie(Constants.stateLogin, stateLoginString);
- cacheStorage.setItemCookie(Constants.loginRequest, loginRequestString);
- cacheStorage.setItemCookie(Constants.stateAcquireToken, stateAcquireTokenString);
- cacheStorage.clearCookie();
- expect(document.cookie).to.be.empty;
- });
});
describe("MSAL Cache Item Management", function () {
+
+ let msalCacheStorage: AuthCache;
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
- sinon.stub(cacheStorage, "cacheLocation").value("sessionStorage");
+ msalCacheStorage = new AuthCache(MSAL_CLIENT_ID,SESSION_STORAGE, true);
setTestCacheItems();
+ document.cookie = "";
});
afterEach(function () {
- cacheStorage.clear();
- sinon.restore();
+ msalCacheStorage.clear();
});
it("getAllAccessTokens returns all accessTokens in cache", function () {
@@ -186,104 +205,122 @@ describe("CacheStorage.ts Class - Session Storage", function () {
at4.accessToken = "accessToken4";
window.sessionStorage.setItem(JSON.stringify(ACCESS_TOKEN_KEY), JSON.stringify(at4));
- let res1 = cacheStorage.getAllAccessTokens(MSAL_CLIENT_ID, "1234");
- let res2 = cacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "1234");
- let res3 = cacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "4567");
+ let res1 = msalCacheStorage.getAllAccessTokens(MSAL_CLIENT_ID, "1234");
+ let res2 = msalCacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "1234");
+ let res3 = msalCacheStorage.getAllAccessTokens("1813e1d1-ad72-46a9-8665-399bba48c201", "4567");
expect(res1).to.be.length(1);
expect(res2).to.be.length(1);
expect(res3).to.be.length(2);
- expect(JSON.stringify(res1[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res1[0].key)));
- expect(JSON.stringify(res2[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res2[0].key)));
- expect(JSON.stringify(res3[0].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res3[0].key)));
- expect(JSON.stringify(res3[1].value)).to.be.eq(cacheStorage.getItem(JSON.stringify(res3[1].key)));
+ expect(JSON.stringify(res1[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res1[0].key)));
+ expect(JSON.stringify(res2[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res2[0].key)));
+ expect(JSON.stringify(res3[0].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res3[0].key)));
+ expect(JSON.stringify(res3[1].value)).to.be.eq(msalCacheStorage.getItem(JSON.stringify(res3[1].key)));
});
it("removeAcquireTokenEntries removes any acquireToken or authorityKey entries in the cache", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
- window.sessionStorage.setItem(acquireTokenAccountKey, JSON.stringify(ACCOUNT));
- window.sessionStorage.setItem(authorityKey, validAuthority);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
+
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey}`, JSON.stringify(ACCOUNT));
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey}`, validAuthority);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
- cacheStorage.removeAcquireTokenEntries();
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
- expect(cacheStorage.getItem(authorityKey)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.null;
});
it("removeAcquireTokenEntries removes specific acquireToken or authorityKey entries in the cache", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
-
- let acquireTokenAccountKey2 = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE2);
- let authorityKey2 = Storage.generateAuthorityKey(TEST_STATE2);
- window.sessionStorage.setItem(acquireTokenAccountKey, JSON.stringify(ACCOUNT));
- window.sessionStorage.setItem(authorityKey, validAuthority);
- window.sessionStorage.setItem(acquireTokenAccountKey2, JSON.stringify(ACCOUNT));
- window.sessionStorage.setItem(authorityKey2, validAuthority);
-
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
-
- cacheStorage.removeAcquireTokenEntries(TEST_STATE);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
+
+ let acquireTokenAccountKey2 = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE2);
+ let authorityKey2 = AuthCache.generateAuthorityKey(TEST_STATE2);
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey}`, JSON.stringify(ACCOUNT));
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey}`, validAuthority);
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${acquireTokenAccountKey2}`, JSON.stringify(ACCOUNT));
+ window.sessionStorage.setItem(`${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${authorityKey2}`, validAuthority);
+
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
+
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE);
- expect(cacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
- expect(cacheStorage.getItem(authorityKey)).to.be.null;
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
- expect(cacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.eq(JSON.stringify(ACCOUNT));
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.eq(validAuthority);
- cacheStorage.removeAcquireTokenEntries(TEST_STATE2);
+ msalCacheStorage.removeAcquireTokenEntries(TEST_STATE2);
- expect(cacheStorage.getItem(acquireTokenAccountKey2)).to.be.null;
- expect(cacheStorage.getItem(authorityKey2)).to.be.null;
+ expect(msalCacheStorage.getItem(acquireTokenAccountKey2)).to.be.null;
+ expect(msalCacheStorage.getItem(authorityKey2)).to.be.null;
+ });
+
+ it("tests clearCookie", function () {
+ let idTokenNonceString = "idTokenNonce";
+ let stateLoginString = "stateLogin";
+ let loginRequestString = "loginRequest";
+ let stateAcquireTokenString = "stateAcquireToken";
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.NONCE_IDTOKEN, idTokenNonceString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.STATE_LOGIN, stateLoginString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.LOGIN_REQUEST, loginRequestString);
+ msalCacheStorage.setItemCookie(TemporaryCacheKeys.STATE_ACQ_TOKEN, stateAcquireTokenString);
+ msalCacheStorage.clearMsalCookie();
+ expect(document.cookie).to.be.empty;
});
- it("resetCacheItems deletes all msal related cache items", function () {
- window.sessionStorage.setItem(Constants.msalClientInfo, "clientInfo");
- window.sessionStorage.setItem(Constants.tokenKeys, "tokenKeys");
- window.sessionStorage.setItem(Constants.stateLogin, "stateLogin");
- window.sessionStorage.setItem(Constants.idTokenKey, "idToken1");
- window.sessionStorage.setItem(Constants.nonceIdToken, "idTokenNonce");
- window.sessionStorage.setItem(Constants.renewStatus + "|RANDOM_GUID", "Completed");
-
- expect(cacheStorage.getItem(Constants.msalClientInfo)).to.be.eq("clientInfo");
- expect(cacheStorage.getItem(Constants.tokenKeys)).to.be.eq("tokenKeys");
- expect(cacheStorage.getItem(Constants.stateLogin)).to.be.eq("stateLogin");
- expect(cacheStorage.getItem(Constants.idTokenKey)).to.be.eq("idToken1");
- expect(cacheStorage.getItem(Constants.nonceIdToken)).to.be.eq("idTokenNonce");
- expect(cacheStorage.getItem(Constants.renewStatus + "|RANDOM_GUID")).to.be.eq("Completed");
-
- cacheStorage.resetCacheItems();
-
- expect(cacheStorage.getItem(Constants.msalClientInfo)).to.be.null;
- expect(cacheStorage.getItem(Constants.tokenKeys)).to.be.null;
- expect(cacheStorage.getItem(Constants.idTokenKey)).to.be.null;
- expect(cacheStorage.getItem(Constants.nonceIdToken)).to.be.null;
- expect(cacheStorage.getItem(Constants.renewStatus)).to.be.null;
- expect(cacheStorage.getItem(Constants.stateLogin)).to.be.null;
+ it("resetCacheItems deletes msal related cache items", function () {
+ let clientInfoKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${PersistentCacheKeys.CLIENT_INFO}`;
+ let stateLoginKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.STATE_LOGIN}`;
+ let idTokenKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${PersistentCacheKeys.IDTOKEN}`;
+ let nonceIdTokenKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}`;
+ let renewStatusKey = `${Constants.cachePrefix}.${MSAL_CLIENT_ID}.${TemporaryCacheKeys.RENEW_STATUS}` + "|RANDOM_GUID";
+
+ window.sessionStorage.setItem(clientInfoKey, "clientInfo");
+ window.sessionStorage.setItem(stateLoginKey, "stateLogin");
+ window.sessionStorage.setItem(idTokenKey, "idToken1");
+ window.sessionStorage.setItem(nonceIdTokenKey, "idTokenNonce");
+ window.sessionStorage.setItem(renewStatusKey, "Completed");
+
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO)).to.be.eq("clientInfo");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN)).to.be.eq("stateLogin");
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.be.eq("idToken1");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.NONCE_IDTOKEN)).to.be.eq("idTokenNonce");
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.RENEW_STATUS + "|RANDOM_GUID")).to.be.eq("Completed");
+
+ msalCacheStorage.resetCacheItems();
+
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.STATE_LOGIN)).to.be.null;
+ expect(msalCacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.NONCE_IDTOKEN)).to.be.null;
+ expect(msalCacheStorage.getItem(TemporaryCacheKeys.RENEW_STATUS + "|RANDOM_GUID")).to.be.null;
});
+ it.skip("tests that resetCacheItems only deletes instance-specific cache items");
});
describe("static key generators", function () {
it("generates acquireToken account key", function () {
- let acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
+ let acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(TEST_ACCOUNT_ID, TEST_STATE);
expect(acquireTokenAccountKey).to.include(TEST_ACCOUNT_ID);
expect(acquireTokenAccountKey).to.include(TEST_STATE);
- expect(acquireTokenAccountKey).to.include(CacheKeys.ACQUIRE_TOKEN_ACCOUNT);
+ expect(acquireTokenAccountKey).to.include(TemporaryCacheKeys.ACQUIRE_TOKEN_ACCOUNT);
});
it("generates authority key", function () {
- let authorityKey = Storage.generateAuthorityKey(TEST_STATE);
+ let authorityKey = AuthCache.generateAuthorityKey(TEST_STATE);
expect(authorityKey).to.include(TEST_STATE);
- expect(authorityKey).to.include(CacheKeys.AUTHORITY);
+ expect(authorityKey).to.include(TemporaryCacheKeys.AUTHORITY);
});
});
diff --git a/lib/msal-core/test/UserAgentApplication.spec.ts b/lib/msal-core/test/UserAgentApplication.spec.ts
index 7daf77fed5..bbba26ba02 100644
--- a/lib/msal-core/test/UserAgentApplication.spec.ts
+++ b/lib/msal-core/test/UserAgentApplication.spec.ts
@@ -16,10 +16,10 @@ import {
} from "../src/index";
import sinon from "sinon";
import { ITenantDiscoveryResponse } from "../src/authority/ITenantDiscoveryResponse";
-import { Storage } from "../src/Storage";
-import { AccessTokenKey } from "../src/AccessTokenKey";
-import { AccessTokenValue } from "../src/AccessTokenValue";
-import { SSOTypes } from "../src/utils/Constants";
+import { AuthCache } from "../src/cache/AuthCache";
+import { AccessTokenKey } from "../src/cache/AccessTokenKey";
+import { AccessTokenValue } from "../src/cache/AccessTokenValue";
+import { SSOTypes, TemporaryCacheKeys, PersistentCacheKeys, ServerHashParamKeys, RequestStatus } from "../src/utils/Constants";
import { WindowUtils } from "../src/utils/WindowUtils";
import { ClientAuthErrorMessage } from "../src/error/ClientAuthError";
import { ClientConfigurationErrorMessage } from "../src/error/ClientConfigurationError";
@@ -105,7 +105,7 @@ describe("UserAgentApplication.ts Class", function () {
sinon.stub(ServerRequestParameters.prototype, "addSSOParameter").returns(params);
};
- let cacheStorage: Storage;
+ let cacheStorage: AuthCache;
let accessTokenKey : AccessTokenKey;
let accessTokenValue : AccessTokenValue;
let account : Account;
@@ -197,6 +197,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Redirect Flow Unit Tests", function () {
beforeEach(function() {
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -205,11 +206,14 @@ describe("UserAgentApplication.ts Class", function () {
};
msal = new UserAgentApplication(config);
setAuthInstanceStubs();
+ setTestCacheItems();
delete window.location;
});
afterEach(function () {
+ cacheStorage.clear();
+ sinon.restore();
window.location = oldWindowLocation;
});
@@ -541,8 +545,8 @@ describe("UserAgentApplication.ts Class", function () {
msal.loginRedirect({});
});
- it("exits login function with error if loginInProgress is true", function (done) {
- sinon.stub(msal, "loginInProgress").value(true);
+ it("exits login function with error if interaction is true", function (done) {
+ cacheStorage.setItem(TemporaryCacheKeys.INTERACTION_STATUS, RequestStatus.IN_PROGRESS);
const checkErrorFromLibrary = function (authErr: AuthError) {
expect(authErr instanceof ClientAuthError).to.be.true;
expect(authErr.errorCode).to.equal(ClientAuthErrorMessage.loginProgressError.code);
@@ -651,7 +655,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Different Callback Signatures", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -663,11 +667,16 @@ describe("UserAgentApplication.ts Class", function () {
setTestCacheItems();
});
+ afterEach(function() {
+ cacheStorage.clear();
+ sinon.restore();
+ });
+
it("Calls the error callback if two callbacks are sent", function (done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, accountState: string) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof ServerError).to.be.true;
expect(error.name).to.include("ServerError");
expect(error.errorCode).to.include(TEST_ERROR_CODE);
@@ -680,14 +689,14 @@ describe("UserAgentApplication.ts Class", function () {
});
it("Calls the token callback if two callbacks are sent", function (done) {
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.nonceIdToken, TEST_NONCE);
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|RANDOM-GUID-HERE|${TEST_USER_STATE_NUM}`, TEST_NONCE);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM);
const checkResponseFromServer = function(response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(response.uniqueId).to.be.eq(TEST_UNIQUE_ID);
- expect(response.tokenType).to.be.eq(Constants.idToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ID_TOKEN);
expect(response.tenantId).to.be.eq(TEST_CONFIG.MSAL_TENANT_ID);
expect(response.accountState).to.include(TEST_USER_STATE_NUM);
done();
@@ -696,14 +705,14 @@ describe("UserAgentApplication.ts Class", function () {
});
it("Calls the response callback if single callback is sent", function (done) {
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.nonceIdToken, TEST_NONCE);
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|RANDOM-GUID-HERE|${TEST_USER_STATE_NUM}`, TEST_NONCE);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM);
const checkResponseFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(response.uniqueId).to.be.eq(TEST_UNIQUE_ID);
- expect(response.tokenType).to.be.eq(Constants.idToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ID_TOKEN);
expect(response.tenantId).to.be.eq(TEST_CONFIG.MSAL_TENANT_ID);
expect(response.accountState).to.include(TEST_USER_STATE_NUM);
done();
@@ -715,7 +724,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Cache Storage Unit Tests", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -748,7 +757,7 @@ describe("UserAgentApplication.ts Class", function () {
expect(response.accessToken).to.be.deep.eq(TEST_TOKENS.ACCESSTOKEN);
expect(response.account).to.be.eq(account);
expect(response.scopes).to.be.deep.eq(tokenRequest.scopes);
- expect(response.tokenType).to.be.eq(Constants.accessToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ACCESS_TOKEN);
done();
}).catch(function(err) {
// Won't happen
@@ -837,7 +846,7 @@ describe("UserAgentApplication.ts Class", function () {
expect(response.idToken.rawIdToken).to.eql(TEST_TOKENS.IDTOKEN_V2);
expect(response.idTokenClaims).to.eql(new IdToken(TEST_TOKENS.IDTOKEN_V2).claims);
expect(response.accessToken).to.eql(TEST_TOKENS.ACCESSTOKEN);
- expect(response.tokenType).to.be.eq(Constants.accessToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ACCESS_TOKEN);
}).catch(function(err: AuthError) {
// Won't happen
console.error("Shouldn't have error here. Data: " + JSON.stringify(err));
@@ -849,7 +858,7 @@ describe("UserAgentApplication.ts Class", function () {
expect(response.idToken.rawIdToken).to.eql(TEST_TOKENS.IDTOKEN_V2);
expect(response.idTokenClaims).to.eql(new IdToken(TEST_TOKENS.IDTOKEN_V2).claims);
expect(response.accessToken).to.include("accessToken2");
- expect(response.tokenType).to.be.eq(Constants.accessToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ACCESS_TOKEN);
done();
}).catch(function(err: AuthError) {
// Won't happen
@@ -1025,7 +1034,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Processing Authentication Responses", function() {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -1044,25 +1053,25 @@ describe("UserAgentApplication.ts Class", function () {
it("tests saveTokenForHash in case of response", function(done) {
const successHash = TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.nonceIdToken, TEST_NONCE);
- cacheStorage.setItem(Constants.urlHash, successHash);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|RANDOM-GUID-HERE|${TEST_USER_STATE_NUM}`, TEST_NONCE);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, successHash);
const checkRespFromServer = function(response: AuthResponse) {
expect(response.uniqueId).to.be.eq(TEST_UNIQUE_ID);
- expect(response.tokenType).to.be.eq(Constants.idToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ID_TOKEN);
expect(response.tenantId).to.be.eq(TEST_CONFIG.MSAL_TENANT_ID);
expect(response.accountState).to.be.eq(TEST_USER_STATE_NUM);
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
done();
};
msal.handleRedirectCallback(checkRespFromServer, errorReceivedCallback);
});
it("tests saveTokenForHash in case of error", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof ServerError).to.be.true;
expect(error.name).to.include("ServerError");
expect(error.errorCode).to.include(TEST_ERROR_CODE);
@@ -1076,10 +1085,10 @@ describe("UserAgentApplication.ts Class", function () {
// TEST_SERVER_ERROR_SUBCODE_CANCEL
it("tests saveTokenForHash in case of non-consentable scopes / return to the application without consenting", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_SERVER_ERROR_SUBCODE_CANCEL + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_SERVER_ERROR_SUBCODE_CANCEL + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof ServerError).to.be.true;
expect(error.name).to.include("ServerError");
expect(error.errorCode).to.include(TEST_ACCESS_DENIED);
@@ -1090,8 +1099,8 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests if you get the state back in errorReceived callback, if state is a number", function (done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorHasState = function(error: AuthError, response: AuthResponse) {
expect(response.accountState).to.include(TEST_USER_STATE_NUM);
done();
@@ -1100,8 +1109,8 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests if you get the state back in errorReceived callback, if state is a url", function (done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_URL);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_URL);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_ERROR_HASH + TEST_USER_STATE_URL);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_URL);
const checkErrorHasState = function(error: AuthError, response: AuthResponse) {
expect(response.accountState).to.include(TEST_USER_STATE_URL);
done();
@@ -1120,39 +1129,39 @@ describe("UserAgentApplication.ts Class", function () {
it("tests that expiresIn returns the correct date for access tokens", function (done) {
sinon.stub(TimeUtils, "now").returns(TEST_TOKEN_LIFETIMES.BASELINE_DATE_CHECK);
- const acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(account.homeAccountIdentifier, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ const acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(account.homeAccountIdentifier, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
cacheStorage.setItem(acquireTokenAccountKey, JSON.stringify(account));
const successHash = TEST_HASHES.TEST_SUCCESS_ACCESS_TOKEN_HASH + TEST_USER_STATE_NUM;
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.nonceIdToken, TEST_NONCE);
- cacheStorage.setItem(Constants.urlHash, successHash);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_ACQ_TOKEN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|RANDOM-GUID-HERE|${TEST_USER_STATE_NUM}`, TEST_NONCE);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, successHash);
const checkRespFromServer = function(response: AuthResponse) {
expect(response.uniqueId).to.be.eq(TEST_UNIQUE_ID);
- expect(response.tokenType).to.be.eq(Constants.accessToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ACCESS_TOKEN);
expect(response.tenantId).to.be.eq(TEST_CONFIG.MSAL_TENANT_ID);
expect(response.accountState).to.be.eq(TEST_USER_STATE_NUM);
expect(response.expiresOn.getTime()).to.be.eq((TEST_TOKEN_LIFETIMES.BASELINE_DATE_CHECK + TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN) * 1000);
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
done();
};
msal.handleRedirectCallback(checkRespFromServer, errorReceivedCallback);
});
it("tests that expiresIn returns the correct date for id tokens", function (done) {
- const acquireTokenAccountKey = Storage.generateAcquireTokenAccountKey(account.homeAccountIdentifier, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ const acquireTokenAccountKey = AuthCache.generateAcquireTokenAccountKey(account.homeAccountIdentifier, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
cacheStorage.setItem(acquireTokenAccountKey, JSON.stringify(account));
const successHash = TEST_HASHES.TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.nonceIdToken, TEST_NONCE);
- cacheStorage.setItem(Constants.urlHash, successHash);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|RANDOM-GUID-HERE|${TEST_USER_STATE_NUM}`, TEST_NONCE);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, successHash);
const checkRespFromServer = function(response: AuthResponse) {
expect(response.uniqueId).to.be.eq(TEST_UNIQUE_ID);
- expect(response.tokenType).to.be.eq(Constants.idToken);
+ expect(response.tokenType).to.be.eq(ServerHashParamKeys.ID_TOKEN);
expect(response.tenantId).to.be.eq(TEST_CONFIG.MSAL_TENANT_ID);
expect(response.accountState).to.be.eq(TEST_USER_STATE_NUM);
expect(response.expiresOn.getTime()).to.be.eq(TEST_TOKEN_LIFETIMES.TEST_ID_TOKEN_EXP * 1000);
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
done();
};
msal.handleRedirectCallback(checkRespFromServer, errorReceivedCallback);
@@ -1162,7 +1171,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("InteractionRequired Error Types", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -1180,10 +1189,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of interaction_required error code", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_INTERACTION_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_INTERACTION_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.interactionRequired.code);
@@ -1196,10 +1205,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of interaction_required error code and description", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_INTERACTION_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_INTERACTION_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.interactionRequired.code);
@@ -1214,10 +1223,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of login_required error code", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_LOGIN_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_LOGIN_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.loginRequired.code);
@@ -1230,10 +1239,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of login_required error code and description", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_LOGIN_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_LOGIN_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.loginRequired.code);
@@ -1248,10 +1257,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of consent_required error code", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_CONSENT_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_CONSENT_REQ_ERROR_HASH1 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.consentRequired.code);
@@ -1264,10 +1273,10 @@ describe("UserAgentApplication.ts Class", function () {
});
it("tests saveTokenForHash in case of consent_required error code and description", function(done) {
- cacheStorage.setItem(Constants.urlHash, TEST_HASHES.TEST_CONSENT_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
- cacheStorage.setItem(Constants.stateLogin, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.URL_HASH, TEST_HASHES.TEST_CONSENT_REQ_ERROR_HASH2 + TEST_USER_STATE_NUM);
+ cacheStorage.setItem(TemporaryCacheKeys.STATE_LOGIN, "RANDOM-GUID-HERE|" + TEST_USER_STATE_NUM);
const checkErrorFromServer = function(error: AuthError, response: AuthResponse) {
- expect(cacheStorage.getItem(Constants.urlHash)).to.be.null;
+ expect(cacheStorage.getItem(TemporaryCacheKeys.URL_HASH)).to.be.null;
expect(error instanceof InteractionRequiredAuthError).to.be.true;
expect(error.name).to.include("InteractionRequiredAuthError");
expect(error.errorCode).to.include(InteractionRequiredAuthErrorMessage.consentRequired.code);
@@ -1285,7 +1294,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Logout functionality", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -1307,8 +1316,8 @@ describe("UserAgentApplication.ts Class", function () {
it("clears cache and account object", function (done) {
cacheStorage.setItem(JSON.stringify(accessTokenKey), JSON.stringify(accessTokenValue));
- cacheStorage.setItem(Constants.idTokenKey, "idTokenKey");
- cacheStorage.setItem(Constants.msalClientInfo, TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED);
+ cacheStorage.setItem(PersistentCacheKeys.IDTOKEN, "idTokenKey");
+ cacheStorage.setItem(PersistentCacheKeys.CLIENT_INFO, TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED);
sinon.stub(Account, "createAccount").returns(account);
window.location = {
...oldWindowLocation,
@@ -1381,7 +1390,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("State Handling", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
const config: Configuration = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
@@ -1417,7 +1426,7 @@ describe("UserAgentApplication.ts Class", function () {
describe("Cache Location", function () {
beforeEach(function () {
- cacheStorage = new Storage("sessionStorage");
+ cacheStorage = new AuthCache(TEST_CONFIG.MSAL_CLIENT_ID, "sessionStorage", true);
setAuthInstanceStubs();
setTestCacheItems();
});
diff --git a/lib/msal-core/test/error/ClientConfigurationError.spec.ts b/lib/msal-core/test/error/ClientConfigurationError.spec.ts
index f177e1fed0..3e3ed8c25b 100644
--- a/lib/msal-core/test/error/ClientConfigurationError.spec.ts
+++ b/lib/msal-core/test/error/ClientConfigurationError.spec.ts
@@ -29,29 +29,9 @@ describe("ClientConfigurationError.ts Class", () => {
expect(err.stack).to.include("ClientConfigurationError.spec.ts");
});
- it("createInvalidCacheLocationConfigError creates a ClientConfigurationError object", () => {
-
- const givenCacheLocation = "mock_storage";
- const invalidCacheError = ClientConfigurationError.createInvalidCacheLocationConfigError(givenCacheLocation);
- let err: ClientConfigurationError;
-
- try {
- throw invalidCacheError;
- } catch (error) {
- err = error;
- }
-
- expect(err.errorCode).to.equal(ClientConfigurationErrorMessage.invalidCacheLocation.code);
- expect(err.errorMessage).to.include(ClientConfigurationErrorMessage.invalidCacheLocation.desc);
- expect(err.errorMessage).to.include(givenCacheLocation);
- expect(err.message).to.include(ClientConfigurationErrorMessage.invalidCacheLocation.desc);
- expect(err.name).to.equal("ClientConfigurationError");
- expect(err.stack).to.include("ClientConfigurationError.spec.ts");
- });
-
it("createNoStorageSupportError creates a ClientConfigurationError object", () => {
- const noStorageSupportError = ClientConfigurationError.createNoStorageSupportedError();
+ const noStorageSupportError = ClientConfigurationError.createStorageNotSupportedError("randomCacheLocation");
let err: ClientConfigurationError;
try {
@@ -60,9 +40,11 @@ describe("ClientConfigurationError.ts Class", () => {
err = error;
}
- expect(err.errorCode).to.equal(ClientConfigurationErrorMessage.noStorageSupported.code);
- expect(err.errorMessage).to.include(ClientConfigurationErrorMessage.noStorageSupported.desc);
- expect(err.message).to.include(ClientConfigurationErrorMessage.noStorageSupported.desc);
+ expect(err.errorCode).to.equal(ClientConfigurationErrorMessage.storageNotSupported.code);
+ expect(err.errorMessage).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(err.message).to.include(ClientConfigurationErrorMessage.storageNotSupported.desc);
+ expect(err.errorMessage).to.include("randomCacheLocation");
+ expect(err.message).to.include("randomCacheLocation");
expect(err.name).to.equal("ClientConfigurationError");
expect(err.stack).to.include("ClientConfigurationError.spec.ts");
});
diff --git a/lib/msal-core/test/utils/UrlUtils.spec.ts b/lib/msal-core/test/utils/UrlUtils.spec.ts
index 2df91ebd35..630099e2ef 100644
--- a/lib/msal-core/test/utils/UrlUtils.spec.ts
+++ b/lib/msal-core/test/utils/UrlUtils.spec.ts
@@ -4,7 +4,7 @@ import { UrlUtils } from "../../src/utils/UrlUtils";
import { TEST_CONFIG, TEST_RESPONSE_TYPE, TEST_URIS } from "../TestConstants";
import { AuthorityFactory } from "../../src/authority/AuthorityFactory";
import { ServerRequestParameters } from "../../src/ServerRequestParameters";
-import { Constants } from "../../src/utils/Constants";
+import { Constants, ServerHashParamKeys } from "../../src/utils/Constants";
describe("UrlUtils.ts class", () => {
@@ -66,23 +66,23 @@ describe("UrlUtils.ts class", () => {
});
describe("urlContainsHash", () => {
- it(Constants.errorDescription, () => {
- const urlString = `http://localhost:3000/#/${Constants.errorDescription}=hello`;
+ it(ServerHashParamKeys.ERROR_DESCRIPTION, () => {
+ const urlString = `http://localhost:3000/#/${ServerHashParamKeys.ERROR_DESCRIPTION}=hello`;
expect(UrlUtils.urlContainsHash(urlString)).to.be.true;
});
- it(Constants.error, () => {
- const urlString = `http://localhost:3000/#/${Constants.error}=hello`;
+ it(ServerHashParamKeys.ERROR, () => {
+ const urlString = `http://localhost:3000/#/${ServerHashParamKeys.ERROR}=hello`;
expect(UrlUtils.urlContainsHash(urlString)).to.be.true;
});
- it(Constants.accessToken, () => {
- const urlString = `http://localhost:3000/#/${Constants.accessToken}=hello`;
+ it(ServerHashParamKeys.ACCESS_TOKEN, () => {
+ const urlString = `http://localhost:3000/#/${ServerHashParamKeys.ACCESS_TOKEN}=hello`;
expect(UrlUtils.urlContainsHash(urlString)).to.be.true;
});
- it(Constants.idToken, () => {
- const urlString = `http://localhost:3000/#/${Constants.idToken}=hello`;
+ it(ServerHashParamKeys.ID_TOKEN, () => {
+ const urlString = `http://localhost:3000/#/${ServerHashParamKeys.ID_TOKEN}=hello`;
expect(UrlUtils.urlContainsHash(urlString)).to.be.true;
});
diff --git a/samples/VanillaJSTestApp/index.html b/samples/VanillaJSTestApp/index.html
index b3d6643c5e..ab5759eddd 100644
--- a/samples/VanillaJSTestApp/index.html
+++ b/samples/VanillaJSTestApp/index.html
@@ -3,7 +3,10 @@
Quickstart for MSAL JS
-
+
+
diff --git a/samples/react-sample-app/src/AuthProvider.js b/samples/react-sample-app/src/AuthProvider.js
index a4d71bd88e..bddebcde9d 100644
--- a/samples/react-sample-app/src/AuthProvider.js
+++ b/samples/react-sample-app/src/AuthProvider.js
@@ -59,25 +59,31 @@ export default C =>
const tokenResponse = await this.acquireToken(
GRAPH_REQUESTS.LOGIN
- );
-
- const graphProfile = await fetchMsGraph(
- GRAPH_ENDPOINTS.ME,
- tokenResponse.accessToken
- ).catch(() => {
+ ).catch(error => {
this.setState({
- error: "Unable to fetch Graph profile."
+ error: error.message
});
});
- if (graphProfile) {
- this.setState({
- graphProfile
+ if (tokenResponse) {
+ const graphProfile = await fetchMsGraph(
+ GRAPH_ENDPOINTS.ME,
+ tokenResponse.accessToken
+ ).catch(() => {
+ this.setState({
+ error: "Unable to fetch Graph profile."
+ });
});
- }
- if (tokenResponse.scopes.includes(GRAPH_SCOPES.MAIL_READ)) {
- return this.readMail(tokenResponse.accessToken);
+ if (graphProfile) {
+ this.setState({
+ graphProfile
+ });
+ }
+
+ if (tokenResponse.scopes.includes(GRAPH_SCOPES.MAIL_READ)) {
+ return this.readMail(tokenResponse.accessToken);
+ }
}
}
}