AzureAD
Uses MSAL 2.0 with PKCE
Uses CDN - alcdn.msauth.net/browser/2.13.1/js/msal-browser.js
link - github.com/Azure-Samples/ms-identity-javascript-v2
link - docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code
link - docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-javascript-auth-code
npm install
npm start
Open the URL - localhost:3000/
index.html
<!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>Microsoft identity platform</title>
<link rel="SHORTCUT ICON" href="./favicon.svg" type="image/x-icon">
<script src="https://alcdn.msauth.net/browser/2.13.1/js/msal-browser.js" integrity="sha384-7hwr87O1w6buPsX92CwuRaz/wQzachgOEq+iLHv0ESavynv6rbYwKImSl7wUW3wV" crossorigin="anonymous"></script>
<script type="text/javascript">
if (typeof Msal === 'undefined')
document.write(unescape("%3Cscript src='https://alcdn.msftauth.net/browser/2.13.1/js/msal-browser.js'
type='text/javascript'
crossorigin='anonymous' %3E%3C/script%3E"));
</script>
<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="/">Microsoft identity platform</a>
<div class="btn-group ml-auto dropleft">
<button type="button" id="SignIn" class="btn btn-secondary" onclick="signIn()">
Sign In
</button>
</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>
<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>
<script type="text/javascript" src="./authConfig.js"></script>
<script type="text/javascript" src="./graphConfig.js"></script>
<script type="text/javascript" src="./ui.js"></script>
<!-- <script type="text/javascript" src="./authRedirect.js"></script> -->
<!-- uncomment the above line and comment the line below if you would like to use the redirect flow -->
<script type="text/javascript" src="./authPopup.js"></script>
<script type="text/javascript" src="./graph.js"></script>
</body>
</html>
ui.js
// Select DOM elements to work with
const welcomeDiv = document.getElementById("WelcomeMessage");
const signInButton = document.getElementById("SignIn");
const cardDiv = document.getElementById("card-div");
const mailButton = document.getElementById("readMail");
const profileButton = document.getElementById("seeProfile");
const profileDiv = document.getElementById("profile-div");
function showWelcomeMessage(username) {
// Reconfiguring DOM elements
cardDiv.style.display = 'initial';
welcomeDiv.innerHTML = `Welcome ${username}`;
signInButton.setAttribute("onclick", "signOut();");
signInButton.setAttribute('class', "btn btn-success")
signInButton.innerHTML = "Sign Out"; }
function updateUI(data, endpoint) {
console.log('Graph API responded at: ' + new Date().toString());
if (endpoint === graphConfig.graphMeEndpoint) {
profileDiv.innerHTML = ''
const title = document.createElement('p');
title.innerHTML = "<strong>Title: </strong>" + data.jobTitle;
const email = document.createElement('p');
email.innerHTML = "<strong>Mail: </strong>" + data.mail;
const phone = document.createElement('p');
phone.innerHTML = "<strong>Phone: </strong>" + data.businessPhones[0];
const address = document.createElement('p');
address.innerHTML = "<strong>Location: </strong>" + data.officeLocation;
profileDiv.appendChild(title);
profileDiv.appendChild(email);
profileDiv.appendChild(phone);
profileDiv.appendChild(address);
} else if (endpoint === graphConfig.graphMailEndpoint) {
if (data.value.length < 1) {
alert("Your mailbox is empty!")
} else {
const tabContent = document.getElementById("nav-tabContent");
const tabList = document.getElementById("list-tab");
tabList.innerHTML = ''; // clear tabList at each readMail call
data.value.map((d, i) => {
// Keeping it simple
if (i < 10) {
const listItem = document.createElement("a");
listItem.setAttribute("class", "list-group-item list-group-item-action")
listItem.setAttribute("id", "list" + i + "list")
listItem.setAttribute("data-toggle", "list")
listItem.setAttribute("href", "#list" + i)
listItem.setAttribute("role", "tab")
listItem.setAttribute("aria-controls", i)
listItem.innerHTML = d.subject;
tabList.appendChild(listItem)
const contentItem = document.createElement("div");
contentItem.setAttribute("class", "tab-pane fade")
contentItem.setAttribute("id", "list" + i)
contentItem.setAttribute("role", "tabpanel")
contentItem.setAttribute("aria-labelledby", "list" + i + "list")
contentItem.innerHTML = "<strong> from: " + d.from.emailAddress.address + "</strong><br><br>" + d.bodyPreview + "...";
tabContent.appendChild(contentItem);
}
});
}
}
}
authConfig.js
const msalConfig = {
auth: {
clientId: "Enter_the_Application_Id_Here",
authority: "Enter_the_Cloud_Instance_Id_HereEnter_the_Tenant_Info_Here",
redirectUri: "Enter_the_Redirect_Uri_Here",
},
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
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case msal.LogLevel.Error:
console.error(message);
return;
case msal.LogLevel.Info:
console.info(message);
return;
case msal.LogLevel.Verbose:
console.debug(message);
return;
case msal.LogLevel.Warning:
console.warn(message);
return;
}
}
}
}
};
// Scopes you add here will be prompted for user consent during sign-in.
// By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
const loginRequest = {
scopes: ["User.Read"]
};
// Add here the scopes to request when obtaining an access token for MS Graph API.
const tokenRequest = {
scopes: ["User.Read", "Mail.Read"],
forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token };
authPopup.js
const myMSALObj = new msal.PublicClientApplication(msalConfig);
let username = "";
function selectAccount() {
const currentAccounts = myMSALObj.getAllAccounts();
if (currentAccounts.length === 0) {
return;
} else if (currentAccounts.length > 1) {
// Add choose account code here
console.warn("Multiple accounts detected.");
} else if (currentAccounts.length === 1) {
username = currentAccounts[0].username;
showWelcomeMessage(username);
}
}
function handleResponse(response) {
if (response !== null) {
username = response.account.username;
showWelcomeMessage(username);
} else {
selectAccount();
}
}
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(handleResponse)
.catch(error => {
console.error(error);
});
}
function signOut() {
const logoutRequest = {
account: myMSALObj.getAccountByUsername(username),
postLogoutRedirectUri: msalConfig.auth.redirectUri,
mainWindowRedirectUri: msalConfig.auth.redirectUri
};
myMSALObj.logoutPopup(logoutRequest);
}
function getTokenPopup(request) {
request.account = myMSALObj.getAccountByUsername(username);
return myMSALObj.acquireTokenSilent(request)
.catch(error => {
console.warn("silent token acquisition fails. acquiring token using popup");
if (error instanceof msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenPopup(request)
.then(tokenResponse => {
console.log(tokenResponse);
return tokenResponse;
}).catch(error => {
console.error(error);
});
} else {
console.warn(error);
}
});
}
function seeProfile() {
getTokenPopup(loginRequest)
.then(response => {
callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
}).catch(error => {
console.error(error);
});
}
function readMail() {
getTokenPopup(tokenRequest)
.then(response => {
callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI);
}).catch(error => {
console.error(error);
});
}
selectAccount();
authRedirect.js
A promise handler needs to be registered for handling the response returned from redirect flow
const myMSALObj = new msal.PublicClientApplication(msalConfig);
let username = "";
myMSALObj.handleRedirectPromise()
.then(handleResponse)
.catch((error) => {
console.error(error);
});
function selectAccount () {
const currentAccounts = myMSALObj.getAllAccounts();
if (currentAccounts.length === 0) {
return;
} else if (currentAccounts.length > 1) {
// Add your account choosing logic here
console.warn("Multiple accounts detected.");
} else if (currentAccounts.length === 1) {
username = currentAccounts[0].username;
showWelcomeMessage(username);
}
}
function handleResponse(response) {
if (response !== null) {
username = response.account.username;
showWelcomeMessage(username);
} else {
selectAccount();
}
}
function signIn() {
myMSALObj.loginRedirect(loginRequest);
}
function signOut() {
const logoutRequest = {
account: myMSALObj.getAccountByUsername(username),
postLogoutRedirectUri: msalConfig.auth.redirectUri,
};
myMSALObj.logoutRedirect(logoutRequest);
}
function getTokenRedirect(request) {
request.account = myMSALObj.getAccountByUsername(username);
return myMSALObj.acquireTokenSilent(request)
.catch(error => {
console.warn("silent token acquisition fails. acquiring token using redirect");
if (error instanceof msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request);
} else {
console.warn(error);
}
});
}
function seeProfile() {
getTokenRedirect(loginRequest)
.then(response => {
callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
}).catch(error => {
console.error(error);
});
}
function readMail() {
getTokenRedirect(tokenRequest)
.then(response => {
callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI);
}).catch(error => {
console.error(error);
});
}
graph.js
Helper function to call MS Graph API endpoint using the authorization bearer token scheme
function callMSGraph(endpoint, token, callback) {
const headers = new Headers();
const bearer = `Bearer ${token}`;
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));
}
graphConfig.js
const graphConfig = {
graphMeEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me",
graphMailEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me/messages"
};
server.js
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const DEFAULT_PORT = process.env.PORT || 3000;
const app = express();
let port = DEFAULT_PORT;
// Configure morgan module to log all requests.
app.use(morgan('dev'));
// Setup app folders.
app.use(express.static('app'));
// Set up a route for index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
// Start the server.
app.listen(port);
console.log(`Listening on port ${port}...`);
© 2022 Better Solutions Limited. All Rights Reserved. © 2022 Better Solutions Limited TopPrevNext