-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1543 from AzureAD/onPageLoadFix-msal-v2
[msal-browser] Add pattern for login on page-load
- Loading branch information
Showing
7 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// 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.PublicClientApplication(msalConfig); | ||
|
||
// Register Callbacks for Redirect flow | ||
myMSALObj.onRedirectAppLoad().then((tokenResponse) => { | ||
const accountObj = tokenResponse ? tokenResponse.account : myMSALObj.getAccount(); | ||
if (accountObj) { | ||
// Account object was retrieved, continue with app progress | ||
console.log('id_token acquired at: ' + new Date().toString()); | ||
showWelcomeMessage(accountObj); | ||
seeProfileRedirect(); | ||
} else if (tokenResponse && tokenResponse.tokenType === "Bearer") { | ||
// No account object available, but access token was retrieved | ||
console.log('access_token acquired at: ' + new Date().toString()); | ||
} else if (tokenResponse === null) { | ||
// tokenResponse was null, attempt sign in or enter unauthenticated state for app | ||
signIn("loginRedirect"); | ||
} else { | ||
console.log("tokenResponse was not null but did not have any tokens: " + tokenResponse); | ||
} | ||
}).catch((error) => { | ||
console.log(error); | ||
}); | ||
|
||
async function signIn(method) { | ||
signInType = isIE ? "loginRedirect" : method; | ||
if (signInType === "loginPopup") { | ||
const loginResponse = await myMSALObj.loginPopup(loginRequest).catch(function (error) { | ||
console.log(error); | ||
}); | ||
console.log(loginResponse); | ||
if (myMSALObj.getAccount()) { | ||
showWelcomeMessage(myMSALObj.getAccount()); | ||
} | ||
} else if (signInType === "loginRedirect") { | ||
myMSALObj.loginRedirect(loginRequest) | ||
} | ||
} | ||
|
||
function signOut() { | ||
myMSALObj.logout(); | ||
} | ||
|
||
async function getTokenPopup(request) { | ||
return await myMSALObj.acquireTokenSilent(request).catch(async (error) => { | ||
console.log("silent token acquisition fails."); | ||
if (error instanceof msal.AuthenticationRequiredError) { | ||
if (msal.AuthenticationRequiredError.isInteractionRequiredError(error.errorCode, error.errorDesc)) { | ||
// fallback to interaction when silent call fails | ||
console.log("acquiring token using popup"); | ||
return myMSALObj.acquireTokenPopup(request).catch(error => { | ||
console.error(error); | ||
}); | ||
} | ||
} else { | ||
console.error(error); | ||
} | ||
}); | ||
} | ||
|
||
// This function can be removed if you do not need to support IE | ||
async function getTokenRedirect(request) { | ||
return await myMSALObj.acquireTokenSilent(request).catch(async (error) => { | ||
console.log("silent token acquisition fails."); | ||
if (error instanceof AuthenticationRequiredError) { | ||
if (AuthenticationRequiredError.isInteractionRequiredError(error.errorCode, error.errorDesc)) { | ||
// fallback to interaction when silent call fails | ||
console.log("acquiring token using redirect"); | ||
myMSALObj.acquireTokenRedirect(request); | ||
} | ||
} else { | ||
console.error(error); | ||
} | ||
}); | ||
} | ||
|
||
async function seeProfileRedirect() { | ||
if (myMSALObj.getAccount()) { | ||
const response = await getTokenRedirect(loginRequest).catch(error => { | ||
console.log(error); | ||
}); | ||
callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI); | ||
profileButton.style.display = 'none'; | ||
} | ||
} | ||
|
||
async function readMailRedirect() { | ||
if (myMSALObj.getAccount()) { | ||
const response = await getTokenRedirect(tokenRequest).catch(error => { | ||
console.log(error); | ||
}); | ||
callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI); | ||
mailButton.style.display = 'none'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Config object to be passed to Msal on creation | ||
const msalConfig = { | ||
auth: { | ||
clientId: "3fba556e-5d4a-48e3-8e1a-fd57c12cb82e", | ||
authority: "https://login.windows-ppe.net/common/" | ||
}, | ||
cache: { | ||
cacheLocation: "sessionStorage", // 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: ["User.Read"] | ||
}; | ||
|
||
// Add here the endpoints for MS Graph API services you would like to use. | ||
const graphConfig = { | ||
graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me", | ||
graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages" | ||
}; | ||
|
||
// Add here scopes for access token to be used at MS Graph API endpoints. | ||
const tokenRequest = { | ||
scopes: ["Mail.Read"], | ||
forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token | ||
}; | ||
|
||
const silentRequest = { | ||
scopes: ["3fba556e-5d4a-48e3-8e1a-fd57c12cb82e", "User.Read", "Mail.Read"] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Helper function to call MS Graph API endpoint | ||
// using authorization bearer token scheme | ||
function callMSGraph(endpoint, accessToken, callback) { | ||
const headers = new Headers(); | ||
const bearer = `Bearer ${accessToken}`; | ||
|
||
headers.append("Authorization", bearer); | ||
|
||
const options = { | ||
method: "GET", | ||
headers: headers | ||
}; | ||
|
||
console.log('request made to Graph API at: ' + new Date().toString()); | ||
|
||
fetch(endpoint, options) | ||
.then(response => response.json()) | ||
.then(response => callback(response, endpoint)) | ||
.catch(error => console.log(error)); | ||
} | ||
|
||
async function seeProfile() { | ||
if (myMSALObj.getAccount()) { | ||
const response = await getTokenPopup(loginRequest).catch(error => { | ||
console.log(error); | ||
}); | ||
callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI); | ||
profileButton.style.display = 'none'; | ||
} | ||
} | ||
|
||
async function readMail() { | ||
if (myMSALObj.getAccount()) { | ||
const response = await getTokenPopup(tokenRequest).catch(error => { | ||
console.log(error); | ||
}); | ||
callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI); | ||
mailButton.style.display = 'none'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> | ||
<title>Quickstart | MSAL.JS Vanilla JavaScript SPA</title> | ||
|
||
<!-- IE support: add promises polyfill before msal.js --> | ||
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/bluebird@3.7.2/js/browser/bluebird.min.js"></script> | ||
<script src="../lib/msal-browser.js"></script> | ||
|
||
<!-- adding Bootstrap 4 for UI components --> | ||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> | ||
<link rel="SHORTCUT ICON" href="https://c.s-microsoft.com/favicon.ico?v2" type="image/x-icon"> | ||
</head> | ||
<body> | ||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> | ||
<a class="navbar-brand" href="/">MS Identity Platform</a> | ||
<div class="btn-group ml-auto dropleft"> | ||
<button type="button" id="SignIn" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||
Sign In | ||
</button> | ||
<div class="dropdown-menu"> | ||
<button class="dropdown-item" id="loginPopup" onclick="signIn(this.id)">Sign in using Popup</button> | ||
<button class="dropdown-item" id="loginRedirect" onclick="signIn(this.id)">Sign in using Redirect</button> | ||
</div> | ||
</div> | ||
</nav> | ||
<br> | ||
<h5 class="card-header text-center">Vanilla JavaScript SPA calling MS Graph API with MSAL.JS</h5> | ||
<br> | ||
<div class="row" style="margin:auto" > | ||
<div id="card-div" class="col-md-3" style="display:none"> | ||
<div class="card text-center"> | ||
<div class="card-body"> | ||
<h5 class="card-title" id="WelcomeMessage">Please sign-in to see your profile and read your mails</h5> | ||
<div id="profile-div"></div> | ||
<br> | ||
<br> | ||
<button class="btn btn-primary" id="seeProfile" onclick="seeProfile()">See Profile</button> | ||
<br> | ||
<br> | ||
<button class="btn btn-primary" id="readMail" onclick="readMail()">Read Mails</button> | ||
</div> | ||
</div> | ||
</div> | ||
<br> | ||
<br> | ||
<div class="col-md-4"> | ||
<div class="list-group" id="list-tab" role="tablist"> | ||
</div> | ||
</div> | ||
<div class="col-md-5"> | ||
<div class="tab-content" id="nav-tabContent"> | ||
</div> | ||
</div> | ||
</div> | ||
<br> | ||
<br> | ||
|
||
<!-- importing bootstrap.js and supporting js libraries --> | ||
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> | ||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> | ||
|
||
<!-- importing app scripts | load order is important --> | ||
<script type="text/javascript" src="./authConfig.js"></script> | ||
<script type="text/javascript" src="./ui.js"></script> | ||
<script type="text/javascript" src="./auth.js"></script> | ||
<script type="text/javascript" src="./graph.js"></script> | ||
</body> | ||
</html> |
66 changes: 66 additions & 0 deletions
66
samples/VanillaJSTestApp2.0/app/onPageLoad/test/template.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import "mocha"; | ||
import puppeteer from "puppeteer"; | ||
import { expect } from "chai"; | ||
import fs from "fs"; | ||
|
||
const SCREENSHOT_BASE_FOLDER_NAME = `${__dirname}/screenshots`; | ||
let SCREENSHOT_NUM = 0; | ||
|
||
function setupScreenshotDir() { | ||
if (!fs.existsSync(`${SCREENSHOT_BASE_FOLDER_NAME}`)) { | ||
fs.mkdirSync(SCREENSHOT_BASE_FOLDER_NAME); | ||
} | ||
} | ||
|
||
async function takeScreenshot(page: puppeteer.Page, testName: string, screenshotName: string): Promise<void> { | ||
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<void> { | ||
await page.waitForNavigation({ waitUntil: "networkidle0"}); | ||
await takeScreenshot(page, testName, `loginPage`); | ||
await page.type("#i0116", "IDLAB@msidlab0.ccsctp.net"); | ||
await page.click("#idSIButton9"); | ||
await page.waitForNavigation({ waitUntil: "networkidle0"}); | ||
await takeScreenshot(page, testName, `pwdInputPage`); | ||
await page.type("#i0118", ""); | ||
await page.click("#idSIButton9"); | ||
} | ||
|
||
describe("Browser tests", function () { | ||
this.timeout(8000); | ||
this.retries(1); | ||
|
||
let browser: puppeteer.Browser; | ||
before(async () => { | ||
setupScreenshotDir(); | ||
browser = await puppeteer.launch({ | ||
headless: true, | ||
ignoreDefaultArgs: ['--no-sandbox', '–disable-setuid-sandbox'] | ||
}); | ||
}); | ||
|
||
let context: puppeteer.BrowserContext; | ||
let page: puppeteer.Page; | ||
beforeEach(async () => { | ||
SCREENSHOT_NUM = 0; | ||
context = await browser.createIncognitoBrowserContext(); | ||
page = await context.newPage(); | ||
await page.goto('http://localhost:30662/'); | ||
}); | ||
|
||
afterEach(async () => { | ||
await page.close(); | ||
}); | ||
|
||
after(async () => { | ||
await context.close(); | ||
await browser.close(); | ||
}); | ||
|
||
// TODO: Add browser tests for sample here | ||
}); |
Oops, something went wrong.