Microsoft Authentication Library for Node - A comprehensive Node.js authentication library for Microsoft identity platform supporting multiple OAuth 2.0 flows
—
MSAL Node supports multiple OAuth 2.0 authentication flows, each designed for specific application scenarios and security requirements. This document covers all supported flows with their request types, usage patterns, and security considerations.
The most secure and recommended flow for most applications, using OAuth 2.0 authorization code grant with PKCE.
/**
* Request to generate authorization URL (first step)
*/
type AuthorizationUrlRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server will redirect after user authorization */
redirectUri: string;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Account to use for authentication */
account?: AccountInfo;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** PKCE code challenge for security */
codeChallenge?: string;
/** PKCE code challenge method */
codeChallengeMethod?: string;
/** State parameter for CSRF protection */
state?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Authority URL to override default */
authority?: string;
/** Response mode (query, fragment, form_post) */
responseMode?: ResponseMode;
};
/**
* Request to exchange authorization code for tokens (second step)
*/
type AuthorizationCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server redirected after user authorization */
redirectUri: string;
/** Authorization code received from authorization server */
code: string;
/** State parameter for CSRF protection validation */
state?: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** PKCE code verifier for security validation */
codeVerifier?: string;
};
/**
* Prompt values for user interaction
*/
enum PromptValue {
/** Force user to enter credentials */
LOGIN = "login",
/** Show account selection screen */
SELECT_ACCOUNT = "select_account",
/** Request user consent */
CONSENT = "consent",
/** No user interaction (silent) */
NONE = "none"
}
/**
* Response modes for authorization response
*/
enum ResponseMode {
/** Return parameters in URL query string */
QUERY = "query",
/** Return parameters in URL fragment */
FRAGMENT = "fragment",
/** Return parameters via HTTP POST */
FORM_POST = "form_post"
}Usage Example:
import { PublicClientApplication, PromptValue } from "@azure/msal-node";
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/common"
}
});
// Step 1: Generate authorization URL
const authCodeUrlParameters = {
scopes: ["openid", "profile", "user.read"],
redirectUri: "http://localhost:3000/redirect",
prompt: PromptValue.SELECT_ACCOUNT,
state: "random-state-value", // CSRF protection
loginHint: "user@domain.com"
};
const authUrl = await pca.getAuthCodeUrl(authCodeUrlParameters);
// Step 2: Exchange code for tokens (after user authorization)
const tokenRequest = {
code: "authorization-code-from-callback",
scopes: ["openid", "profile", "user.read"],
redirectUri: "http://localhost:3000/redirect",
state: "random-state-value"
};
const response = await pca.acquireTokenByCode(tokenRequest);App-only authentication flow for server applications and daemon apps that don't require user interaction.
/**
* Request for client credentials flow (app-only authentication)
*/
type ClientCredentialRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Client assertion JWT for certificate-based authentication */
clientAssertion?: string | (() => string);
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};Usage Example:
import { ConfidentialClientApplication } from "@azure/msal-node";
const cca = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientSecret: "your-client-secret", // Or use clientCertificate
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"],
skipCache: false // Use cache if available
};
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
// Use token for Microsoft Graph API calls
const graphData = await fetch("https://graph.microsoft.com/v1.0/users", {
headers: {
"Authorization": `Bearer ${response.accessToken}`
}
});Authentication flow for devices with limited input capabilities or no browser.
/**
* Request for device code flow authentication
*/
type DeviceCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Callback function to display device code to user */
deviceCodeCallback: (response: DeviceCodeResponse) => void;
/** Flag to cancel the device code flow polling */
cancel?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional query parameters for the device authorization request */
extraQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Response containing device code information for user
*/
type DeviceCodeResponse = {
/** Device code for internal polling */
deviceCode: string;
/** User-friendly code to display to user */
userCode: string;
/** URL where user should navigate to enter the user code */
verificationUri: string;
/** Complete verification URL including user code (optional) */
verificationUriComplete?: string;
/** Number of seconds the device code is valid */
expiresIn: number;
/** Minimum number of seconds to wait between polling requests */
interval: number;
/** Message to display to user */
message: string;
};Usage Example:
const deviceCodeRequest = {
scopes: ["user.read"],
deviceCodeCallback: (response) => {
console.log("=== Device Code Authentication ===");
console.log("Please open a web browser and navigate to:");
console.log(response.verificationUri);
console.log("Enter the following code:");
console.log(response.userCode);
console.log("Or visit this URL directly:");
console.log(response.verificationUriComplete);
console.log(`Code expires in ${response.expiresIn} seconds`);
}
};
try {
const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
if (response) {
console.log("Authentication successful!");
console.log("Access token:", response.accessToken);
}
} catch (error) {
console.error("Device code flow failed:", error);
}Flow for middle-tier services to exchange user tokens for tokens to downstream services.
/**
* Request for on-behalf-of flow for middle-tier services
*/
type OnBehalfOfRequest = {
/** User assertion (JWT) from the upstream service */
oboAssertion: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};Usage Example:
// Express.js middleware API that calls downstream services
app.post("/api/user-data", async (req, res) => {
const userToken = req.headers.authorization?.replace("Bearer ", "");
if (!userToken) {
return res.status(401).json({ error: "No authorization token" });
}
const oboRequest = {
oboAssertion: userToken,
scopes: ["https://graph.microsoft.com/user.read", "https://graph.microsoft.com/mail.read"]
};
try {
const response = await cca.acquireTokenOnBehalfOf(oboRequest);
if (response) {
// Call Microsoft Graph on behalf of the user
const userProfile = await fetch("https://graph.microsoft.com/v1.0/me", {
headers: {
"Authorization": `Bearer ${response.accessToken}`
}
});
const userData = await userProfile.json();
res.json(userData);
} else {
res.status(500).json({ error: "Failed to acquire OBO token" });
}
} catch (error) {
console.error("OBO flow failed:", error);
res.status(500).json({ error: "Authentication failed" });
}
});Acquire tokens without user interaction using cached tokens or refresh tokens.
/**
* Request for silent token acquisition from cache
*/
type SilentFlowRequest = {
/** Account to acquire token for */
account: AccountInfo;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Force refresh from authority instead of using cache */
forceRefresh?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};Usage Example:
// Get cached accounts
const accounts = await pca.getAllAccounts();
if (accounts.length > 0) {
const silentRequest = {
account: accounts[0],
scopes: ["user.read"],
forceRefresh: false
};
try {
const response = await pca.acquireTokenSilent(silentRequest);
console.log("Token acquired silently:", response.accessToken);
} catch (error) {
if (error.errorCode === "interaction_required") {
// Fall back to interactive flow
console.log("Silent acquisition failed, user interaction required");
}
}
}Exchange refresh tokens for new access tokens.
/**
* Request for refresh token flow
*/
type RefreshTokenRequest = {
/** Refresh token to exchange for new tokens */
refreshToken: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
/** Force cache refresh for migration scenarios */
forceCache?: boolean;
};Usage Example:
// Refresh token stored from previous authentication
const storedRefreshToken = getStoredRefreshToken();
const refreshRequest = {
refreshToken: storedRefreshToken,
scopes: ["user.read", "mail.read"]
};
try {
const response = await pca.acquireTokenByRefreshToken(refreshRequest);
if (response) {
console.log("Token refreshed successfully");
// Update stored tokens
storeTokens(response);
}
} catch (error) {
console.error("Token refresh failed:", error);
// May need to prompt user for re-authentication
}Browser-based interactive authentication for desktop applications.
/**
* Request for interactive token acquisition with browser
*/
type InteractiveRequest = {
/** Function to open browser with authorization URL */
openBrowser: (url: string) => Promise<void>;
/** Array of scopes the application is requesting access to */
scopes?: string[];
/** HTML template for success page */
successTemplate?: string;
/** HTML template for error page */
errorTemplate?: string;
/** Window handle for desktop applications */
windowHandle?: Buffer;
/** Custom loopback client for handling redirects */
loopbackClient?: ILoopbackClient;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Account to use for authentication */
account?: AccountInfo;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Interface for custom loopback client implementations
*/
interface ILoopbackClient {
/** Start listening for authorization response */
listenForAuthCode(authCodeUrl: string, port?: number): Promise<string>;
/** Get the redirect URI for the loopback server */
getRedirectUri(): string;
/** Close the loopback server */
closeServer(): void;
}Usage Example:
import { open } from "open"; // npm package for opening URLs
const interactiveRequest = {
scopes: ["user.read", "mail.read"],
openBrowser: async (url: string) => {
console.log("Opening browser...");
await open(url);
},
successTemplate: `
<html>
<body>
<h1>Authentication Successful!</h1>
<p>You can close this window and return to the application.</p>
</body>
</html>
`,
prompt: PromptValue.SELECT_ACCOUNT
};
try {
const response = await pca.acquireTokenInteractive(interactiveRequest);
console.log("Interactive authentication successful!");
console.log("User:", response.account?.username);
} catch (error) {
console.error("Interactive authentication failed:", error);
}Resource Owner Password Credentials (ROPC) flow - deprecated for security reasons.
/**
* Request for username/password flow (deprecated)
* @deprecated Use more secure flows like authorization code or device code
*/
type UsernamePasswordRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Username for authentication */
username: string;
/** Password for authentication */
password: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};Usage Example (Not Recommended):
// ⚠️ This flow is deprecated and should be avoided for security reasons
const usernamePasswordRequest = {
scopes: ["user.read"],
username: "user@domain.com",
password: "user-password"
};
try {
const response = await pca.acquireTokenByUsernamePassword(usernamePasswordRequest);
// This flow bypasses multi-factor authentication and modern security features
} catch (error) {
console.error("Username/password authentication failed:", error);
}/**
* Security best practices for different flows
*/
type SecurityConsiderations = {
/** Always use PKCE for authorization code flow */
usePKCE: boolean;
/** Validate state parameter for CSRF protection */
validateState: boolean;
/** Use secure storage for refresh tokens */
secureTokenStorage: boolean;
/** Implement proper token caching */
implementCaching: boolean;
/** Use certificate-based authentication when possible */
useCertificates: boolean;
/** Avoid username/password flow */
avoidROPC: boolean;
};Recommended Flow Selection:
Flows to Avoid:
Install with Tessl CLI
npx tessl i tessl/npm-azure--msal-node