How to migrate a JavaScript app from ADAL.js to MSAL.js
Microsoft Authentication Library for JavaScript (MSAL.js, also known as msal-browser
) 2.x is the authentication library we recommend using with JavaScript applications on the Microsoft identity platform. This article highlights the changes you need to make to migrate an app that uses the ADAL.js to use MSAL.js 2.x
Note
We strongly recommend MSAL.js 2.x over MSAL.js 1.x. The auth code grant flow is more secure and allows single-page applications to maintain a good user experience despite the privacy measures browsers like Safari have implemented to block 3rd party cookies, among other benefits.
Prerequisites
- You must set the Platform / Reply URL Type to Single-page application on App Registration portal (if you have other platforms added in your app registration, such as Web, you need to make sure the redirect URIs don't overlap. See: Redirect URI restrictions)
- You must provide polyfills for ES6 features that MSAL.js relies on (for example, promises) in order to run your apps on Internet Explorer
- Migrate your Microsoft Entra apps to v2 endpoint if you haven't already
Install and import MSAL
There are two ways to install the MSAL.js 2.x library:
Via npm:
npm install @azure/msal-browser
Then, depending on your module system, import it as shown below:
import * as msal from "@azure/msal-browser"; // ESM
const msal = require('@azure/msal-browser'); // CommonJS
Via CDN:
Load the script in the header section of your HTML document:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"></script>
</head>
</html>
For alternative CDN links and best practices when using CDN, see: CDN Usage
Initialize MSAL
In ADAL.js, you instantiate the AuthenticationContext class, which then exposes the methods you can use to achieve authentication (login
, acquireTokenPopup
, and so on). This object serves as the representation of your application's connection to the authorization server or identity provider. When initializing, the only mandatory parameter is the clientId:
window.config = {
clientId: "YOUR_CLIENT_ID"
};
var authContext = new AuthenticationContext(config);
In MSAL.js, you instantiate the PublicClientApplication class instead. Like ADAL.js, the constructor expects a configuration object that contains the clientId
parameter at minimum. See for more: Initialize MSAL.js
const msalConfig = {
auth: {
clientId: 'YOUR_CLIENT_ID'
}
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
In both ADAL.js and MSAL.js, the authority URI defaults to https://login.partner.microsoftonline.cn/common
if you don't specify it.
Note
If you use the https://login.partner.microsoftonline.cn/common
authority in v2.0, you will allow users to sign in with any Microsoft Entra organization. In MSAL.js, if you want to restrict login to any Microsoft Entra account (same behavior as with ADAL.js), use https://login.partner.microsoftonline.cn/organizations
instead.
Configure MSAL
Some of the configuration options in ADAL.js that are used when initializing AuthenticationContext are deprecated in MSAL.js, while some new ones are introduced. See the full list of available options. Importantly, many of these options, except for clientId
, can be overridden during token acquisition, allowing you to set them on a per-request basis. For instance, you can use a different authority URI or redirect URI than the one you set during initialization when acquiring tokens.
Additionally, you no longer need to specify the login experience (that is, whether using pop-up windows or redirecting the page) via the configuration options. Instead, MSAL.js
exposes loginPopup
and loginRedirect
methods through the PublicClientApplication
instance.
Enable logging
In ADAL.js, you configure logging separately at any place in your code:
window.config = {
clientId: "YOUR_CLIENT_ID"
};
var authContext = new AuthenticationContext(config);
var Logging = {
level: 3,
log: function (message) {
console.log(message);
},
piiLoggingEnabled: false
};
authContext.log(Logging)
In MSAL.js, logging is part of the configuration options and is created during the initialization of PublicClientApplication
:
const msalConfig = {
auth: {
// authentication related parameters
},
cache: {
// cache related parameters
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
}
const msalInstance = new msal.PublicClientApplication(msalConfig);
Switch to MSAL API
Some of the public methods in ADAL.js have equivalents in MSAL.js:
ADAL | MSAL | Notes |
---|---|---|
acquireToken |
acquireTokenSilent |
Renamed and now expects an account object |
acquireTokenPopup |
acquireTokenPopup |
Now async and returns a promise |
acquireTokenRedirect |
acquireTokenRedirect |
Now async and returns a promise |
handleWindowCallback |
handleRedirectPromise |
Needed if using redirect experience |
getCachedUser |
getAllAccounts |
Renamed and now returns an array of accounts. |
Others were deprecated, while MSAL.js offers new methods:
ADAL | MSAL | Notes |
---|---|---|
login |
N/A | Deprecated. Use loginPopup or loginRedirect |
logOut |
N/A | Deprecated. Use logoutPopup or logoutRedirect |
N/A | loginPopup |
|
N/A | loginRedirect |
|
N/A | logoutPopup |
|
N/A | logoutRedirect |
|
N/A | getAccountByHomeId |
Filters accounts by home ID (oid + tenant ID) |
N/A | getAccountLocalId |
Filters accounts by local ID (useful for ADFS) |
N/A | getAccountUsername |
Filters accounts by username (if exists) |
In addition, as MSAL.js is implemented in TypeScript unlike ADAL.js, it exposes various types and interfaces that you can make use of in your projects. See the MSAL.js API reference for more.
Use scopes instead of resources
An important difference between the Azure Active Directory v1.0 versus 2.0 endpoints is about how the resources are accessed. When using ADAL.js with the v1.0 endpoint, you would first register a permission on app registration portal, and then request an access token for a resource (such as Microsoft Graph) as shown below:
authContext.acquireTokenRedirect("https://microsoftgraph.chinacloudapi.cn", function (error, token) {
// do something with the access token
});
MSAL.js supports only the v2.0 endpoint. The v2.0 endpoint employs a scope-centric model to access resources. Thus, when you request an access token for a resource, you also need to specify the scope for that resource:
msalInstance.acquireTokenRedirect({
scopes: ["https://microsoftgraph.chinacloudapi.cn/User.Read"]
});
One advantage of the scope-centric model is the ability to use dynamic scopes. When building applications using the v1.0 endpoint, you needed to register the full set of permissions (called static scopes) required by the application for the user to consent to at the time of login. In v2.0, you can use the scope parameter to request the permissions at the time you want them (hence, dynamic scopes). This allows the user to provide incremental consent to scopes. So if at the beginning you just want the user to sign in to your application and you don’t need any kind of access, you can do so. If later you need the ability to read the calendar of the user, you can then request the calendar scope in the acquireToken methods and get the user's consent. See for more: Resources and scopes
Use promises instead of callbacks
In ADAL.js, callbacks are used for any operation after the authentication succeeds and a response is obtained:
authContext.acquireTokenPopup(resource, extraQueryParameter, claims, function (error, token) {
// do something with the access token
});
In MSAL.js, promises are used instead:
msalInstance.acquireTokenPopup({
scopes: ["https://microsoftgraph.chinacloudapi.cn/User.Read"] // shorthand for https://microsoftgraph.chinacloudapi.cn/User.Read
}).then((response) => {
// do something with the auth response
}).catch((error) => {
// handle errors
});
You can also use the async/await syntax that comes with ES8:
const getAccessToken = async() => {
try {
const authResponse = await msalInstance.acquireTokenPopup({
scopes: ["https://microsoftgraph.chinacloudapi.cn/User.Read"]
});
} catch (error) {
// handle errors
}
}
Cache and retrieve tokens
Like ADAL.js, MSAL.js caches tokens and other authentication artifacts in browser storage, using the Web Storage API. You're recommended to use sessionStorage
option (see: configuration) because it's more secure in storing tokens that are acquired by your users, but localStorage
will give you Single Sign On across tabs and user sessions.
Importantly, you aren't supposed to access the cache directly. Instead, you should use an appropriate MSAL.js API for retrieving authentication artifacts like access tokens or user accounts.
Renew tokens with refresh tokens
ADAL.js uses the OAuth 2.0 implicit flow, which doesn't return refresh tokens for security reasons (refresh tokens have longer lifetime than access tokens and are therefore more dangerous in the hands of malicious actors). Hence, ADAL.js performs token renewal using a hidden IFrame so that the user isn't repeatedly prompted to authenticate.
With the auth code flow with PKCE support, apps using MSAL.js 2.x obtain refresh tokens along with ID and access tokens, which can be used to renew them. The usage of refresh tokens is abstracted away, and the developers aren't supposed to build logic around them. Instead, MSAL manages token renewal using refresh tokens by itself. Your previous token cache with ADAL.js won't be transferable to MSAL.js, as the token cache schema has changed and incompatible with the schema used in ADAL.js.
Handle errors and exceptions
When using MSAL.js, the most common type of error you might face is the interaction_in_progress
error. This error is thrown when an interactive API (loginPopup
, loginRedirect
, acquireTokenPopup
, acquireTokenRedirect
) is invoked while another interactive API is still in progress. The login*
and acquireToken*
APIs are async so you'll need to ensure that the resulting promises have resolved before invoking another one.
Another common error is interaction_required
. This error is often resolved by initiating an interactive token acquisition prompt. For instance, the web API you're trying to access might have a Conditional Access policy in place, requiring the user to perform multifactor authentication (MFA). In that case, handling interaction_required
error by triggering acquireTokenPopup
or acquireTokenRedirect
will prompt the user for MFA, allowing them to fullfil it.
Yet another common error you might face is consent_required
, which occurs when permissions required for obtaining an access token for a protected resource aren't consented by the user. As in interaction_required
, the solution for consent_required
error is often initiating an interactive token acquisition prompt, using either acquireTokenPopup
or acquireTokenRedirect
.
See for more: Common MSAL.js errors and how to handle them
Use the Events API
MSAL.js (>=v2.4) introduces an events API that you can make use of in your apps. These events are related to the authentication process and what MSAL is doing at any moment, and can be used to update UI, show error messages, check if any interaction is in progress and so on. For instance, below is an event callback that will be called when login process fails for any reason:
const callbackId = msalInstance.addEventCallback((message) => {
// Update UI or interact with EventMessage here
if (message.eventType === EventType.LOGIN_FAILURE) {
if (message.error instanceof AuthError) {
// Do something with the error
}
}
});
For performance, it's important to unregister event callbacks when they're no longer needed. See for more: MSAL.js Events API
Handle multiple accounts
ADAL.js has the concept of a user to represent the currently authenticated entity. MSAL.js replaces users with accounts, given the fact that a user can have more than one account associated with them. This also means that you now need to control for multiple accounts and choose the appropriate one to work with. The snippet below illustrates this process:
let homeAccountId = null; // Initialize global accountId (can also be localAccountId or username) used for account lookup later, ideally stored in app state
// This callback is passed into `acquireTokenPopup` and `acquireTokenRedirect` to handle the interactive auth response
function handleResponse(resp) {
if (resp !== null) {
homeAccountId = resp.account.homeAccountId; // alternatively: resp.account.homeAccountId or resp.account.username
} else {
const currentAccounts = myMSALObj.getAllAccounts();
if (currentAccounts.length < 1) { // No cached accounts
return;
} else if (currentAccounts.length > 1) { // Multiple account scenario
// Add account selection logic here
} else if (currentAccounts.length === 1) {
homeAccountId = currentAccounts[0].homeAccountId; // Single account scenario
}
}
}
For more information, see: Accounts in MSAL.js
Use the wrappers libraries
If you're developing for Angular and React frameworks, you can use MSAL Angular v2 and MSAL React, respectively. These wrappers expose the same public API as MSAL.js while offering framework-specific methods and components that can streamline the authentication and token acquisition processes.
Run the app
Once your changes are done, run the app and test your authentication scenario:
npm start
Example: Securing a SPA with ADAL.js vs. MSAL.js
The snippets below demonstrates the minimal code required for a single-page application authenticating users with the Microsoft identity platform and getting an access token for Microsoft Graph using first ADAL.js and then MSAL.js:
Using ADAL.js | Using MSAL.js |
|
|