diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/b2c/auth.js b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/auth.js
new file mode 100644
index 0000000000..13d6323c85
--- /dev/null
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/auth.js
@@ -0,0 +1,93 @@
+// Browser check variables
+// If you support IE, our recommendation is that you sign-in using Redirect APIs
+// If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
+const ua = window.navigator.userAgent;
+const msie = ua.indexOf("MSIE ");
+const msie11 = ua.indexOf("Trident/");
+const msedge = ua.indexOf("Edge/");
+const isIE = msie > 0 || msie11 > 0;
+const isEdge = msedge > 0;
+
+let signInType;
+
+// Create the main myMSALObj instance
+// configuration parameters are located at authConfig.js
+const myMSALObj = new Msal.UserAgentApplication(msalConfig);
+
+// Register Callbacks for Redirect flow
+myMSALObj.handleRedirectCallback(authRedirectCallBack);
+
+function authRedirectCallBack(error, response) {
+ if (error) {
+ console.log(error);
+ } else {
+ if (response.tokenType === "id_token" && myMSALObj.getAccount() && !myMSALObj.isCallback(window.location.hash)) {
+ console.log('id_token acquired at: ' + new Date().toString());
+ showWelcomeMessage(myMSALObj.getAccount());
+ } else if (response.tokenType === "access_token") {
+ console.log('access_token acquired at: ' + new Date().toString());
+ updateUI(response);
+ accessTokenButtonPopup.style.display = 'none';
+ accessTokenButtonRedirect.style.display = 'none';
+ } else {
+ console.log("token type is:" + response.tokenType);
+ }
+ }
+}
+
+// Redirect: once login is successful and redirects with tokens, call Graph API
+if (myMSALObj.getAccount() && !myMSALObj.isCallback(window.location.hash)) {
+ // avoid duplicate code execution on page load in case of iframe and Popup window.
+ showWelcomeMessage(myMSALObj.getAccount());
+}
+
+function signIn(method) {
+ signInType = isIE ? "loginRedirect" : method;
+ if (signInType === "loginPopup") {
+ myMSALObj.loginPopup(loginRequest)
+ .then(loginResponse => {
+ console.log(loginResponse);
+ if (myMSALObj.getAccount()) {
+ showWelcomeMessage(myMSALObj.getAccount());
+ }
+ }).catch(function (error) {
+ console.log(error);
+ });
+ } else if (signInType === "loginRedirect") {
+ myMSALObj.loginRedirect(loginRequest)
+ }
+}
+
+function signOut() {
+ myMSALObj.logout();
+}
+
+function getAccessTokenPopup() {
+ if (myMSALObj.getAccount()) {
+ myMSALObj.acquireTokenPopup(loginRequest).then(response => {
+ updateUI(response);
+ accessTokenButtonPopup.style.display = 'none';
+ accessTokenButtonRedirect.style.display = 'none';
+ }).catch(error => {
+ console.log(error);
+ });
+ }
+}
+
+function getAccessTokenRedirect() {
+ if (myMSALObj.getAccount()) {
+ myMSALObj.acquireTokenRedirect(loginRequest);
+ }
+}
+
+function getAccessTokenSilent() {
+ if (myMSALObj.getAccount()) {
+ myMSALObj.acquireTokenSilent(loginRequest).then(response => {
+ updateUI(response);
+ accessTokenButtonPopup.style.display = 'none';
+ accessTokenButtonRedirect.style.display = 'none';
+ }).catch(error => {
+ console.log(error);
+ })
+ }
+}
\ No newline at end of file
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/b2c/authConfig.js b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/authConfig.js
new file mode 100644
index 0000000000..915a3dcda7
--- /dev/null
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/authConfig.js
@@ -0,0 +1,18 @@
+// Config object to be passed to Msal on creation
+const msalConfig = {
+ auth: {
+ clientId: "e3b9ad76-9763-4827-b088-80c7a7888f79",
+ authority: "https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_SISOPolicy/",
+ knownAuthorities: ["login.microsoftonline.com"]
+ },
+ cache: {
+ cacheLocation: "localStorage", // This configures where your cache will be stored
+ storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
+ }
+};
+
+// Add here scopes for id token to be used at MS Identity Platform endpoints.
+const loginRequest = {
+ scopes: ["https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"],
+ forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token
+};
\ No newline at end of file
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/b2c/index.html b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/index.html
new file mode 100644
index 0000000000..dd569d1470
--- /dev/null
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/index.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+ Quickstart | MSAL.JS Vanilla JavaScript SPA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Please sign-in to see your profile and read your mails
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/b2c/test/browser.spec.ts b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/test/browser.spec.ts
new file mode 100644
index 0000000000..2bfb6a7a83
--- /dev/null
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/test/browser.spec.ts
@@ -0,0 +1,244 @@
+import * as Mocha from "mocha";
+import puppeteer from "puppeteer";
+import { expect } from "chai";
+import fs from "fs";
+import { LabClient, ILabApiParams } from "../../../e2eTests/LabClient";
+
+const SCREENSHOT_BASE_FOLDER_NAME = `${__dirname}/screenshots`;
+let SCREENSHOT_NUM = 0;
+let username = "";
+let accountPwd = "";
+
+// Set App Info
+const clientId = "e3b9ad76-9763-4827-b088-80c7a7888f79";
+const authority = "https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_SISOPolicy/"
+const scopes = ["https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"]
+const idTokenCacheKey = "msal." + clientId + ".idtoken"
+const clientInfoCacheKey = "msal." + clientId + ".client.info"
+
+function setupScreenshotDir() {
+ if (!fs.existsSync(`${SCREENSHOT_BASE_FOLDER_NAME}`)) {
+ fs.mkdirSync(SCREENSHOT_BASE_FOLDER_NAME);
+ }
+}
+
+async function setupCredentials() {
+ const testCreds = new LabClient();
+ const userParams: ILabApiParams = {envName: "azurecloud"};
+ const envResponse = await testCreds.getUserVarsByCloudEnvironment(userParams);
+ const testEnv = envResponse[0];
+ if (testEnv.upn) {
+ username = testEnv.upn;
+ }
+
+ const testPwdSecret = await testCreds.getSecret(testEnv.labName);
+
+ accountPwd = testPwdSecret.value;
+}
+
+async function takeScreenshot(page: puppeteer.Page, testName: string, screenshotName: string): Promise {
+ const screenshotFolderName = `${SCREENSHOT_BASE_FOLDER_NAME}/${testName}`
+ if (!fs.existsSync(`${screenshotFolderName}`)) {
+ fs.mkdirSync(screenshotFolderName);
+ }
+ await page.screenshot({ path: `${screenshotFolderName}/${++SCREENSHOT_NUM}_${screenshotName}.png` });
+}
+
+async function enterCredentials(page: puppeteer.Page, testName: string): Promise {
+ await page.waitForNavigation({ waitUntil: "networkidle0"});
+ await page.waitForSelector("#i0116");
+ await takeScreenshot(page, testName, `loginPage`);
+ await page.type("#i0116", username);
+ await page.click("#idSIButton9");
+ await page.waitForNavigation({ waitUntil: "networkidle0"});
+ await page.waitForSelector("#i0118");
+ await takeScreenshot(page, testName, `pwdInputPage`);
+ await page.type("#i0118", accountPwd);
+ await page.click("#idSIButton9");
+
+ // Keep me signed in dialog box
+ await page.waitForSelector("#idSIButton9");
+ await page.click("#idSIButton9");
+}
+
+async function loginRedirect(page: puppeteer.Page, testName: string): Promise {
+ // Home Page
+ await takeScreenshot(page, testName, `samplePageInit`);
+ // Click Sign In
+ await page.click("#SignIn");
+ await takeScreenshot(page, testName, `signInClicked`);
+ // Click Sign In With Redirect
+ await page.click("#loginRedirect");
+ await page.waitForSelector("#MSIDLAB4_AzureAD");
+ await takeScreenshot(page, testName, "b2cSignInPage");
+ // Select Lab Provider
+ await page.click("#MSIDLAB4_AzureAD");
+
+ // Enter credentials
+ await enterCredentials(page, testName);
+ // Wait for return to page
+ await page.waitForSelector("#getAccessTokenRedirect");
+ await takeScreenshot(page, testName, `samplePageLoggedIn`);
+}
+
+async function loginPopup(page: puppeteer.Page, testName: string): Promise {
+ // Home Page
+ await takeScreenshot(page, testName, `samplePageInit`);
+ // Click Sign In
+ await page.click("#SignIn");
+ await takeScreenshot(page, testName, `signInClicked`);
+ // Click Sign In With Popup
+ const newPopupWindowPromise = new Promise(resolve => page.once('popup', resolve));
+ await page.click("#loginPopup");
+ const popupPage = await newPopupWindowPromise;
+ const popupWindowClosed = new Promise(resolve => popupPage.once("close", resolve));
+
+ await popupPage.waitForSelector("#MSIDLAB4_AzureAD");
+ await takeScreenshot(popupPage, testName, "b2cSignInPage");
+ // Select Lab Provider
+ await popupPage.click("#MSIDLAB4_AzureAD");
+
+ // Enter credentials
+ await enterCredentials(popupPage, testName);
+ // Wait until popup window closes and see that we are logged in
+ await popupWindowClosed;
+ await page.waitForSelector("#getAccessTokenPopup");
+ await takeScreenshot(page, testName, `samplePageLoggedIn`);
+}
+
+async function validateAccessTokens(page: puppeteer.Page, localStorage: Storage) {
+ let accessTokensFound = 0
+ let accessTokenMatch: boolean;
+
+ Object.keys(localStorage).forEach(async (key) => {
+ if (key.includes("authority")) {
+ let cacheKey = JSON.parse(key);
+ let cachedScopeList = cacheKey.scopes.split(" ");
+
+ accessTokenMatch = cacheKey.authority === authority.toLowerCase() &&
+ cacheKey.clientId.toLowerCase() === clientId.toLowerCase() &&
+ scopes.every(scope => cachedScopeList.includes(scope));
+
+ if (accessTokenMatch) {
+ accessTokensFound += 1;
+ await page.evaluate((key) => window.localStorage.removeItem(key))
+ }
+ }
+ });
+
+ return accessTokensFound;
+}
+
+describe("Browser tests", function () {
+ this.timeout(8000);
+ this.retries(1);
+
+ let browser: puppeteer.Browser;
+ before(async () => {
+ setupScreenshotDir();
+ setupCredentials();
+ browser = await puppeteer.launch({
+ headless: true,
+ ignoreDefaultArgs: ['--no-sandbox', '–disable-setuid-sandbox']
+ });
+ });
+
+ let context: puppeteer.BrowserContext;
+ let page: puppeteer.Page;
+
+ after(async () => {
+ await context.close();
+ await browser.close();
+ });
+
+ describe("Test Login functions", async () => {
+ beforeEach(async () => {
+ SCREENSHOT_NUM = 0;
+ context = await browser.createIncognitoBrowserContext();
+ page = await context.newPage();
+ await page.goto('http://localhost:30662/');
+ });
+
+ afterEach(async () => {
+ await page.close();
+ });
+
+ it("Performs loginRedirect", async () => {
+ const testName = "redirectBaseCase";
+ await loginRedirect(page, testName);
+
+ const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
+
+ expect(Object.keys(localStorage)).to.contain(idTokenCacheKey);
+ expect(Object.keys(localStorage)).to.contain(clientInfoCacheKey);
+ });
+
+ it("Performs loginPopup", async () => {
+ const testName = "popupBaseCase";
+ await loginPopup(page, testName);
+
+ const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
+ expect(Object.keys(localStorage)).to.contain(idTokenCacheKey);
+ expect(Object.keys(localStorage)).to.contain(clientInfoCacheKey);
+ });
+ });
+
+ describe("Test AcquireToken functions", async () => {
+ const testName = "acquireTokenBaseCase";
+
+ before(async () => {
+ SCREENSHOT_NUM = 0;
+ context = await browser.createIncognitoBrowserContext();
+ page = await context.newPage();
+ await page.goto('http://localhost:30662/');
+ await loginPopup(page, testName);
+ });
+
+ after(async () => {
+ await page.close();
+ });
+
+ afterEach(async () => {
+ await page.reload();
+ });
+
+ it("Test acquireTokenRedirect", async () => {
+ await page.click("#getAccessTokenRedirect");
+ await page.waitForSelector("#access-token-info");
+ await takeScreenshot(page, testName, "accessTokenAcquiredRedirect");
+
+ const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
+ expect(Object.keys(localStorage)).to.contain(idTokenCacheKey);
+ expect(Object.keys(localStorage)).to.contain(clientInfoCacheKey);
+
+ const accessTokensFound = await validateAccessTokens(page, localStorage);
+ expect(accessTokensFound).to.equal(1);
+ });
+
+ it("Test acquireTokenPopup", async () => {
+ await page.click("#getAccessTokenPopup");
+ await page.waitForSelector("#access-token-info");
+ await takeScreenshot(page, testName, "accessTokenAcquiredPopup");
+
+ const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
+ expect(Object.keys(localStorage)).to.contain(idTokenCacheKey);
+ expect(Object.keys(localStorage)).to.contain(clientInfoCacheKey);
+
+ const accessTokensFound = await validateAccessTokens(page, localStorage);
+ expect(accessTokensFound).to.equal(1);
+ });
+
+ it("Test acquireTokenSilent", async () => {
+ await page.click("#getAccessTokenSilent");
+ await page.waitForSelector("#access-token-info");
+ await takeScreenshot(page, testName, "accessTokenAcquiredSilently");
+
+ const localStorage = await page.evaluate(() => Object.assign({}, window.localStorage));
+ expect(Object.keys(localStorage)).to.contain(idTokenCacheKey);
+ expect(Object.keys(localStorage)).to.contain(clientInfoCacheKey);
+
+ const accessTokensFound = await validateAccessTokens(page, localStorage);
+ expect(accessTokensFound).to.equal(1);
+ });
+ });
+});
\ No newline at end of file
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/b2c/ui.js b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/ui.js
new file mode 100644
index 0000000000..a6af5fa69f
--- /dev/null
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/b2c/ui.js
@@ -0,0 +1,33 @@
+// Select DOM elements to work with
+const welcomeDiv = document.getElementById("WelcomeMessage");
+const signInButton = document.getElementById("SignIn");
+const cardDiv = document.getElementById("card-div");
+const accessTokenButtonRedirect = document.getElementById("getAccessTokenRedirect");
+const accessTokenButtonPopup = document.getElementById("getAccessTokenPopup");
+const accessTokenButtonSilent = document.getElementById("getAccessTokenSilent");
+const profileDiv = document.getElementById("profile-div");
+
+function showWelcomeMessage(account) {
+ // Reconfiguring DOM elements
+ cardDiv.style.display = 'initial';
+ welcomeDiv.innerHTML = `Welcome ${account.name}`;
+ signInButton.nextElementSibling.style.display = 'none';
+ signInButton.setAttribute("onclick", "signOut();");
+ signInButton.setAttribute('class', "btn btn-success")
+ signInButton.innerHTML = "Sign Out";
+}
+
+function updateUI(response) {
+ const oldAccessTokenDiv = document.getElementById('access-token-info');
+ if (oldAccessTokenDiv) {
+ oldAccessTokenDiv.remove();
+ }
+ const accessTokenDiv = document.createElement('div');
+ accessTokenDiv.id = "access-token-info";
+ profileDiv.appendChild(accessTokenDiv);
+
+ const scopes = document.createElement('p');
+ scopes.innerHTML = "Access Token Acquired for Scopes: " + response.scopes;
+
+ accessTokenDiv.appendChild(scopes);
+}
\ No newline at end of file
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/default/test/browser.spec.ts b/samples/msal-core-samples/VanillaJSTestApp/app/default/test/browser.spec.ts
index e7236b8ffc..c976da0952 100644
--- a/samples/msal-core-samples/VanillaJSTestApp/app/default/test/browser.spec.ts
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/default/test/browser.spec.ts
@@ -2,7 +2,7 @@ import * as Mocha from "mocha";
import puppeteer from "puppeteer";
import { expect } from "chai";
import fs from "fs";
-import { LabClient } from "../../../e2eTests/LabClient";
+import { LabClient, ILabApiParams } from "../../../e2eTests/LabClient";
const SCREENSHOT_BASE_FOLDER_NAME = `${__dirname}/screenshots`;
let SCREENSHOT_NUM = 0;
@@ -17,7 +17,8 @@ function setupScreenshotDir() {
async function setupCredentials() {
const testCreds = new LabClient();
- const envResponse = await testCreds.getUserVarsByCloudEnvironment("azurecloud");
+ const userParams: ILabApiParams = {envName: "azurecloud"};
+ const envResponse = await testCreds.getUserVarsByCloudEnvironment(userParams);
const testEnv = envResponse[0];
if (testEnv.upn) {
username = testEnv.upn;
diff --git a/samples/msal-core-samples/VanillaJSTestApp/app/get_access_token_interactively/test/browser.spec.ts b/samples/msal-core-samples/VanillaJSTestApp/app/get_access_token_interactively/test/browser.spec.ts
index d2373b5ad0..e793c0f944 100644
--- a/samples/msal-core-samples/VanillaJSTestApp/app/get_access_token_interactively/test/browser.spec.ts
+++ b/samples/msal-core-samples/VanillaJSTestApp/app/get_access_token_interactively/test/browser.spec.ts
@@ -2,7 +2,7 @@ import * as Mocha from "mocha";
import puppeteer from "puppeteer";
import { expect } from "chai";
import fs from "fs";
-import { LabClient } from "../../../e2eTests/LabClient";
+import { LabClient, ILabApiParams } from "../../../e2eTests/LabClient";
const SCREENSHOT_BASE_FOLDER_NAME = `${__dirname}/screenshots`;
let SCREENSHOT_NUM = 0;
@@ -17,7 +17,8 @@ function setupScreenshotDir() {
async function setupCredentials() {
const testCreds = new LabClient();
- const envResponse = await testCreds.getUserVarsByCloudEnvironment("azurecloud");
+ const userParams: ILabApiParams = {envName: "azurecloud"};
+ const envResponse = await testCreds.getUserVarsByCloudEnvironment(userParams);
const testEnv = envResponse[0];
if (testEnv.upn) {
username = testEnv.upn;
diff --git a/samples/msal-core-samples/VanillaJSTestApp/e2eTests/LabClient.ts b/samples/msal-core-samples/VanillaJSTestApp/e2eTests/LabClient.ts
index 566625af30..4baa72b9ae 100644
--- a/samples/msal-core-samples/VanillaJSTestApp/e2eTests/LabClient.ts
+++ b/samples/msal-core-samples/VanillaJSTestApp/e2eTests/LabClient.ts
@@ -2,6 +2,12 @@ import { ClientSecretCredential, AccessToken } from "@azure/identity";
import axios from "axios";
const labApiUri = "https://msidlab.com/api"
+export interface ILabApiParams {
+ envName?: string,
+ userType?: string,
+ b2cProvider?: string
+};
+
export class LabClient {
private credentials: ClientSecretCredential;
@@ -35,10 +41,26 @@ export class LabClient {
return null;
}
- async getUserVarsByCloudEnvironment(envName: string): Promise {
+ async getUserVarsByCloudEnvironment(apiParams: ILabApiParams): Promise {
const accessToken = await this.getCurrentToken();
+ let queryParams: Array = [];
+
+ if (apiParams.envName) {
+ queryParams.push(`envname=${apiParams.envName}`);
+ }
+ if (apiParams.userType) {
+ queryParams.push(`usertype=${apiParams.userType}`);
+ }
+ if (apiParams.b2cProvider) {
+ queryParams.push(`b2cprovider=${apiParams.b2cProvider}`);
+ }
+
+ if (queryParams.length <= 0) {
+ throw "Must provide at least one param to getUserVarsByCloudEnvironment";
+ }
+ const apiUrl = '/user?' + queryParams.join("&");
- return await this.requestLabApi(`/user?azureenvironment=${envName}`, accessToken);
+ return await this.requestLabApi(apiUrl, accessToken);
}
async getSecret(secretName: string): Promise {