CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-azure--msal-node

Microsoft Authentication Library for Node - A comprehensive Node.js authentication library for Microsoft identity platform supporting multiple OAuth 2.0 flows

Pending
Overview
Eval results
Files

authentication-flows.mddocs/

Authentication 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.

Capabilities

Authorization Code Flow

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);

Client Credentials Flow

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}`
  }
});

Device Code Flow

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);
}

On-Behalf-Of Flow

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" });
  }
});

Silent Token Acquisition

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");
    }
  }
}

Refresh Token Flow

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
}

Interactive Flow

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);
}

Username/Password Flow (Deprecated)

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);
}

Flow Selection Guide

Public Client Applications

  • Authorization Code Flow: Web apps, SPAs with backend
  • Interactive Flow: Desktop applications
  • Device Code Flow: IoT devices, CLI tools, smart TVs
  • Silent Flow: Token refresh for all scenarios

Confidential Client Applications

  • Client Credentials Flow: Daemon apps, background services
  • Authorization Code Flow: Web applications with server-side code
  • On-Behalf-Of Flow: Middle-tier APIs calling downstream services
  • Silent Flow: Token refresh and cache utilization

Security Considerations

/**
 * 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:

  1. Server Applications: Client Credentials Flow
  2. Web Applications: Authorization Code Flow
  3. Desktop Applications: Interactive Flow or Authorization Code Flow
  4. Mobile Applications: Authorization Code Flow with PKCE
  5. IoT/CLI Applications: Device Code Flow
  6. Middle-tier APIs: On-Behalf-Of Flow
  7. Background Services: Client Credentials Flow

Flows to Avoid:

  • Username/Password Flow (deprecated and insecure)
  • Implicit Flow (less secure than authorization code with PKCE)

Install with Tessl CLI

npx tessl i tessl/npm-azure--msal-node

docs

authentication-flows.md

confidential-client.md

configuration.md

error-handling.md

index.md

managed-identity.md

public-client.md

token-cache.md

tile.json