npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mohsinonxrm/dataverse-sdk-auth-msal-browser

v1.0.0

Published

> MSAL Browser authentication provider for Dataverse SDK. Implements `AccessTokenProvider` using `@azure/msal-browser` for Single Page Applications (SPAs). Supports popup and redirect authentication flows with automatic silent token refresh.

Downloads

12

Readme

@mohsinonxrm/dataverse-sdk-auth-msal-browser

MSAL Browser authentication provider for Dataverse SDK. Implements AccessTokenProvider using @azure/msal-browser for Single Page Applications (SPAs). Supports popup and redirect authentication flows with automatic silent token refresh.

Features

  • Popup & Redirect Flows - Choose interaction type: popup windows or full-page redirects
  • Silent Token Acquisition - Automatic token refresh without user interaction (MSAL handles internally)
  • React Integration - Works seamlessly with @azure/msal-react and React apps
  • Multi-Account Support - Handle multiple signed-in accounts with active account tracking
  • TypeScript-first - Fully typed with comprehensive IntelliSense
  • Conditional Access - Support for claims challenges and step-up authentication
  • Browser Storage - SessionStorage or LocalStorage for token caching

Installation

pnpm add @mohsinonxrm/dataverse-sdk-auth-msal-browser @mohsinonxrm/dataverse-sdk-core @azure/msal-browser

Peer dependencies:

  • @azure/msal-browser (^3.0.0)
  • @mohsinonxrm/dataverse-sdk-core (workspace dependency)

Quick Start

1. Basic Setup with Popup Flow (Recommended)

Best for: Most SPA scenarios, React apps, Vue apps, Angular apps

Popup flow shows login in a popup window, keeping the main app page loaded.

import { PublicClientApplication } from "@azure/msal-browser";
import { MsalBrowserTokenProvider } from "@mohsinonxrm/dataverse-sdk-auth-msal-browser";
import { DataverseClient } from "@mohsinonxrm/dataverse-sdk-core";

// Step 1: Configure MSAL
const msalConfig = {
  auth: {
    clientId: "your-client-id", // Azure AD App Registration client ID
    authority: "https://login.microsoftonline.com/your-tenant-id",
    redirectUri: window.location.origin, // Must match app registration
  },
  cache: {
    cacheLocation: "sessionStorage", // 'sessionStorage' or 'localStorage'
    storeAuthStateInCookie: false, // Set to true for IE11/Edge legacy
  },
};

// Step 2: Initialize MSAL (required before use)
const pca = new PublicClientApplication(msalConfig);
await pca.initialize();

// Step 3: Create token provider with popup interaction
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  interactionType: "popup", // Default: popup
});

// Step 4: Create Dataverse client
const client = new DataverseClient({
  baseUrl: "https://your-org.crm.dynamics.com",
  tokenProvider,
});

// Step 5: Use the client - first call triggers popup login automatically
const accounts = await client.api("/accounts").select("name", "accountnumber").top(10).get();

console.log(`Retrieved ${accounts.value.length} accounts`);

How it works:

  1. First API call triggers getToken()
  2. Provider attempts silent token acquisition
  3. If no cached token, popup window opens for login
  4. User authenticates in popup
  5. Token cached for future requests
  6. Subsequent calls use cached token (automatic refresh)

2. Redirect Flow

Best for: Mobile browsers where popups may be blocked, full-page authentication UX

Redirect flow navigates the entire page to login, then redirects back to your app.

import { PublicClientApplication } from "@azure/msal-browser";
import { MsalBrowserTokenProvider } from "@mohsinonxrm/dataverse-sdk-auth-msal-browser";
import { DataverseClient } from "@mohsinonxrm/dataverse-sdk-core";

const msalConfig = {
  auth: {
    clientId: "your-client-id",
    authority: "https://login.microsoftonline.com/your-tenant-id",
    redirectUri: window.location.origin,
  },
  cache: {
    cacheLocation: "sessionStorage",
  },
};

const pca = new PublicClientApplication(msalConfig);
await pca.initialize();

// IMPORTANT: Handle redirect promise after page loads
const tokenResponse = await pca.handleRedirectPromise();
if (tokenResponse) {
  console.log("User signed in via redirect:", tokenResponse.account.username);
  pca.setActiveAccount(tokenResponse.account);
}

// Create token provider with redirect interaction
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  interactionType: "redirect",
});

const client = new DataverseClient({
  baseUrl: "https://your-org.crm.dynamics.com",
  tokenProvider,
});

// First API call will redirect entire page to login
// After login, user is redirected back to redirectUri
// handleRedirectPromise() captures the result
const accounts = await client.api("/accounts").top(10).get();

Redirect flow lifecycle:

  1. App loads → handleRedirectPromise() checks for redirect result
  2. If no auth → API call triggers acquireTokenRedirect()
  3. Page redirects to Microsoft login
  4. User authenticates
  5. Page redirects back to redirectUri
  6. handleRedirectPromise() resolves with tokens
  7. Token cached, app continues

3. Pre-authenticated Setup (Recommended for Production)

Best for: Apps with dedicated login page/flow, explicit auth control

Login explicitly before creating the Dataverse client.

import { PublicClientApplication } from "@azure/msal-browser";
import { MsalBrowserTokenProvider } from "@mohsinonxrm/dataverse-sdk-auth-msal-browser";
import { DataverseClient } from "@mohsinonxrm/dataverse-sdk-core";

const pca = new PublicClientApplication(msalConfig);
await pca.initialize();

// Explicit login via popup
const loginResponse = await pca.loginPopup({
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  prompt: "select_account", // Force account picker
});

// Set as active account
pca.setActiveAccount(loginResponse.account);
console.log(`Signed in as: ${loginResponse.account.username}`);

// Create token provider with specific account
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  account: loginResponse.account, // Use specific account
  interactionType: "popup",
});

const client = new DataverseClient({
  baseUrl: "https://your-org.crm.dynamics.com",
  tokenProvider,
});

// All API calls use silent token acquisition (no popup after initial login)
const whoami = await client.api("/WhoAmI").get();
console.log(`User ID: ${whoami.UserId}`);

React Integration

With @azure/msal-react (Recommended)

The @azure/msal-react library provides React-specific hooks and components for MSAL authentication.

import React from 'react';
import { MsalProvider, useMsal, AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalBrowserTokenProvider } from '@mohsinonxrm/dataverse-sdk-auth-msal-browser';
import { DataverseClient } from '@mohsinonxrm/dataverse-sdk-core';

// MSAL configuration
const msalConfig = {
  auth: {
    clientId: process.env.REACT_APP_CLIENT_ID!,
    authority: `https://login.microsoftonline.com/${process.env.REACT_APP_TENANT_ID}`,
    redirectUri: window.location.origin,
  },
  cache: {
    cacheLocation: 'sessionStorage',
  },
};

const pca = new PublicClientApplication(msalConfig);
await pca.initialize(); // Initialize before rendering

// App wrapper with MsalProvider
export function App() {
  return (
    <MsalProvider instance={pca}>
      <AuthenticatedTemplate>
        <DataverseApp />
      </AuthenticatedTemplate>
      <UnauthenticatedTemplate>
        <LoginButton />
      </UnauthenticatedTemplate>
    </MsalProvider>
  );
}

// Login button component
function LoginButton() {
  const { instance } = useMsal();

  const handleLogin = () => {
    instance.loginPopup({
      scopes: [process.env.REACT_APP_DATAVERSE_SCOPE!],
    });
  };

  return <button onClick={handleLogin}>Sign In</button>;
}

// Component using Dataverse client
function DataverseApp() {
  const { instance, accounts } = useMsal();
  const [client, setClient] = React.useState<DataverseClient | null>(null);
  const [accounts, setAccounts] = React.useState([]);

  React.useEffect(() => {
    if (accounts.length > 0) {
      // Create token provider with first account
      const tokenProvider = new MsalBrowserTokenProvider(instance, {
        scopes: [process.env.REACT_APP_DATAVERSE_SCOPE!],
        account: accounts[0],
        interactionType: 'popup',
      });

      const dataverseClient = new DataverseClient({
        baseUrl: process.env.REACT_APP_DATAVERSE_URL!,
        tokenProvider,
      });

      setClient(dataverseClient);
    }
  }, [instance, accounts]);

  React.useEffect(() => {
    if (!client) return;

    // Fetch accounts from Dataverse
    client.api('/accounts')
      .select('name', 'accountnumber')
      .top(10)
      .get()
      .then(result => setAccounts(result.value))
      .catch(console.error);
  }, [client]);

  if (!client) {
    return <div>Initializing...</div>;
  }

  return (
    <div>
      <h1>Dataverse Accounts</h1>
      <ul>
        {accounts.map(acc => (
          <li key={acc.accountid}>{acc.name}</li>
        ))}
      </ul>
    </div>
  );
}

Custom React Hook for Dataverse Client

Create a reusable hook for the Dataverse client.

import { useMsal } from '@azure/msal-react';
import { DataverseClient } from '@mohsinonxrm/dataverse-sdk-core';
import { MsalBrowserTokenProvider } from '@mohsinonxrm/dataverse-sdk-auth-msal-browser';
import { useMemo } from 'react';

export function useDataverseClient(): DataverseClient | null {
  const { instance, accounts } = useMsal();

  return useMemo(() => {
    if (accounts.length === 0) {
      return null;
    }

    const tokenProvider = new MsalBrowserTokenProvider(instance, {
      scopes: [process.env.REACT_APP_DATAVERSE_SCOPE!],
      account: accounts[0],
      interactionType: 'popup',
    });

    return new DataverseClient({
      baseUrl: process.env.REACT_APP_DATAVERSE_URL!,
      tokenProvider,
    });
  }, [instance, accounts]);
}

// Usage in component
function AccountList() {
  const client = useDataverseClient();
  const [accounts, setAccounts] = React.useState([]);

  React.useEffect(() => {
    if (!client) return;

    client.api('/accounts')
      .select('name', 'accountnumber')
      .top(10)
      .get()
      .then(result => setAccounts(result.value))
      .catch(console.error);
  }, [client]);

  if (!client) {
    return <div>Not authenticated</div>;
  }

  return (
    <div>
      {accounts.map(acc => (
        <div key={acc.accountid}>{acc.name}</div>
      ))}
    </div>
  );
}
  account: accounts[0],
  interactionType: 'popup',
});

return new DataverseClient({
  baseUrl: process.env.REACT_APP_DATAVERSE_URL!,
  tokenProvider,
});

}, [instance, accounts]); }

// Usage in component function AccountList() { const client = useDataverseClient(); const [accounts, setAccounts] = React.useState([]);

React.useEffect(() => { if (!client) return;

client.api('/accounts').top(10).get()
  .then(setAccounts)
  .catch(console.error);

}, [client]);

if (!client) { return Not authenticated; }

return {/_ Render accounts _/}; }


## Account Management

### Multi-Account Scenarios

Handle multiple signed-in accounts in your SPA.

```typescript
const pca = new PublicClientApplication(msalConfig);
await pca.initialize();

const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ['https://your-org.crm.dynamics.com/.default'],
});

// Get all cached accounts
const accounts = tokenProvider.getAccounts();
console.log(`Found ${accounts.length} cached account(s)`);

accounts.forEach(account => {
  console.log(`- ${account.username} (${account.name})`);
});

// Switch to a different account
if (accounts.length > 1) {
  tokenProvider.setActiveAccount(accounts[1]);
  console.log(`Switched to account: ${accounts[1].username}`);
}

// Get currently active account
const activeAccount = tokenProvider.getActiveAccount();
if (activeAccount) {
  console.log(`Active account: ${activeAccount.username}`);
} else {
  console.log('No active account set');
}

// Use specific account for token provider
const specificTokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ['https://your-org.crm.dynamics.com/.default'],
  account: accounts[0], // Use first account explicitly
});

Sign Out

Sign out the current user or a specific account.

const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  interactionType: "popup", // Determines sign-out method
});

// Sign out current active account (popup)
await tokenProvider.signOut();
console.log("User signed out via popup");

// Sign out specific account
const accounts = tokenProvider.getAccounts();
if (accounts.length > 0) {
  await tokenProvider.signOut(accounts[0]);
}

// Redirect-based sign out
const redirectTokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  interactionType: "redirect",
});

await redirectTokenProvider.signOut();
// Page redirects to Microsoft logout, then back to app

// Sign out and clear all accounts
const allAccounts = pca.getAllAccounts();
for (const account of allAccounts) {
  await pca.logoutPopup({ account });
}

Sign out behavior:

  • interactionType: 'popup'logoutPopup() (popup window)
  • interactionType: 'redirect'logoutRedirect() (full page redirect)
  • Clears tokens from browser cache
  • Ends session with Microsoft identity platform

API Reference

Class: MsalBrowserTokenProvider

Implements AccessTokenProvider interface from @mohsinonxrm/dataverse-sdk-core.

Constructor

constructor(
  publicClientApplication: IPublicClientApplication,
  options: MsalBrowserTokenProviderOptions
)

Parameters:

  • publicClientApplication - Initialized MSAL PublicClientApplication or IPublicClientApplication
    • Must call await pca.initialize() before passing to constructor
  • options - Configuration options (see below)

Throws:

  • DataverseInvalidRequestError if parameters are invalid or missing

Methods

async getToken(scopes?, options?): Promise<string>

Acquires access token using silent flow first, falls back to interactive (popup/redirect) if needed.

// Use configured scopes
const token = await tokenProvider.getToken();

// Override scopes for this request
const token = await tokenProvider.getToken(["https://org2.crm.dynamics.com/.default"]);

// With additional options
const token = await tokenProvider.getToken(undefined, {
  claims: '{"access_token":{"acrs":{"essential":true,"values":["c1"]}}}',
  correlationId: crypto.randomUUID(),
});

getAccounts(): AccountInfo[]

Returns all accounts cached by MSAL.

const accounts = tokenProvider.getAccounts();
console.log(`Found ${accounts.length} account(s)`);

setActiveAccount(account: AccountInfo): void

Sets the active account for MSAL operations.

const accounts = tokenProvider.getAccounts();
tokenProvider.setActiveAccount(accounts[0]);

getActiveAccount(): AccountInfo | null

Gets the currently active account.

const activeAccount = tokenProvider.getActiveAccount();
if (activeAccount) {
  console.log(`Active: ${activeAccount.username}`);
}

async signOut(account?: AccountInfo): Promise<void>

Signs out a user using popup or redirect (based on interactionType).

// Sign out active account
await tokenProvider.signOut();

// Sign out specific account
const accounts = tokenProvider.getAccounts();
await tokenProvider.signOut(accounts[0]);

Configuration Options

MsalBrowserTokenProviderOptions

interface MsalBrowserTokenProviderOptions {
  scopes: string[]; // Required
  account?: AccountInfo; // Optional
  interactionType?: "popup" | "redirect"; // Default: 'popup'
  claims?: string; // Optional
  correlationId?: string; // Optional
}

| Property | Type | Required | Default | Description | | ----------------- | ----------------------- | -------- | ------------- | ----------------------------------------- | | scopes | string[] | ✅ Yes | - | OAuth scopes for Dataverse access | | account | AccountInfo | ❌ No | First account | Specific account for silent auth | | interactionType | 'popup' \| 'redirect' | ❌ No | 'popup' | How to handle interactive auth | | claims | string | ❌ No | - | JSON string for Conditional Access claims | | correlationId | string | ❌ No | - | Correlation ID for tracing |

Common Scopes

| Scope Pattern | Description | Example | | --------------------------------------------------- | ------------------------------------- | ----------------------------------------------------- | | https://<org>.crm.dynamics.com/.default | All permissions from app registration | https://contoso.crm.dynamics.com/.default | | https://<org>.crm.dynamics.com/user_impersonation | Act on behalf of signed-in user | https://contoso.crm.dynamics.com/user_impersonation |

Recommendation: Use .default scope for most scenarios. It requests all permissions granted to your app registration.

Advanced Scenarios

Conditional Access & Claims Challenges

Handle Conditional Access policies that require additional claims or step-up authentication.

// Scenario: Dataverse protected by Conditional Access requiring device compliance
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  claims: JSON.stringify({
    access_token: {
      acrs: {
        essential: true,
        values: ["c1"], // Compliance claim
      },
    },
  }),
});

// Or pass claims at token acquisition time (dynamic claims challenge)
try {
  await client.api("/accounts").get();
} catch (error) {
  // If Dataverse returns 401 with claims challenge
  if (error.status === 401 && error.headers["www-authenticate"]) {
    const claimsChallenge = parseClaimsFromHeader(error.headers["www-authenticate"]);

    const token = await tokenProvider.getToken(undefined, {
      claims: claimsChallenge,
    });

    // Retry request with new token
  }
}

Custom Correlation IDs for Tracing

Add correlation IDs for distributed tracing and debugging.

// Set correlation ID in provider options
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://your-org.crm.dynamics.com/.default"],
  correlationId: crypto.randomUUID(),
});

// Or per-request correlation ID
const token = await tokenProvider.getToken(undefined, {
  correlationId: "my-request-correlation-id-123",
});

Scope Override Per Request

Use different scopes for specific requests (multi-org scenarios).

// Default provider for org1
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://org1.crm.dynamics.com/.default"],
});

const client1 = new DataverseClient({
  baseUrl: "https://org1.crm.dynamics.com",
  tokenProvider,
});

// Override scopes for org2 access
const org2Token = await tokenProvider.getToken(["https://org2.crm.dynamics.com/.default"]);

// Use org2Token manually or create separate client
const client2 = new DataverseClient({
  baseUrl: "https://org2.crm.dynamics.com",
  tokenProvider: new MsalBrowserTokenProvider(pca, {
    scopes: ["https://org2.crm.dynamics.com/.default"],
  }),
});

Error Handling

import { DataverseAuthenticationError } from "@mohsinonxrm/dataverse-sdk-core";

try {
  const client = new DataverseClient({
    baseUrl: "https://your-org.crm.dynamics.com",
    tokenProvider,
  });

  const accounts = await client.api("/accounts").get();
} catch (error) {
  if (error instanceof DataverseAuthenticationError) {
    console.error("Authentication failed:", error.message);
    // Handle auth error (e.g., show login button)
  } else {
    console.error("API call failed:", error);
  }
}

Browser Compatibility

  • ✅ Chrome 90+
  • ✅ Edge 90+
  • ✅ Firefox 88+
  • ✅ Safari 14+

Note: Requires browsers with native fetch and Promise support. For older browsers, use polyfills.

Storage Options

Session Storage (Recommended)

const msalConfig = {
  cache: {
    cacheLocation: "sessionStorage", // Tokens cleared on tab close
    storeAuthStateInCookie: false,
  },
};

Local Storage (Persistent)

const msalConfig = {
  cache: {
    cacheLocation: "localStorage", // Tokens persist across browser sessions
    storeAuthStateInCookie: false,
  },
};

Cookies (IE 11 Support)

const msalConfig = {
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true, // For IE 11 and Edge Legacy
  },
};

Security Best Practices

  1. Use sessionStorage for cache in most scenarios
  2. Set restrictive CORS policies on your web server
  3. Use HTTPS in production (required by MSAL)
  4. Configure redirect URI to specific pages, not wildcards
  5. Enable PKCE (automatic in MSAL Browser)
  6. Validate tokens server-side for sensitive operations
  7. Don't store tokens in localStorage if avoidable
  8. Don't expose client secrets in browser code (use public client)

Troubleshooting

Popup blocked by browser

Problem: Login popup is blocked

Solution:

  • Ensure login is triggered by user action (button click)
  • Or switch to redirect flow
const tokenProvider = new MsalBrowserTokenProvider(pca, {
  scopes: ["https://org.crm.dynamics.com/.default"],
  interactionType: "redirect", // Use redirect instead
});

"Interaction required" errors

Problem: Silent token acquisition fails

Solution: This is normal behavior. The provider automatically falls back to popup/redirect.

CORS errors

Problem: No 'Access-Control-Allow-Origin' header

Solution: Dataverse automatically allows CORS from your app's redirect URI origin. Ensure:

  1. Redirect URI is correctly registered
  2. Requests originate from that URI
  3. Origin header is sent by browser

Token expiration

Problem: API calls fail after some time

Solution: Token refresh is automatic. If seeing errors:

// Force new token
const newToken = await tokenProvider.getToken(
  ["https://org.crm.dynamics.com/.default"],
  { forceRefresh: true } // Note: Not supported by default, handle manually
);

Related Packages

  • @mohsinonxrm/dataverse-sdk-core - Core SDK (required)
  • @mohsinonxrm/dataverse-sdk-auth-msal-node - MSAL for Node.js apps
  • @mohsinonxrm/dataverse-sdk-auth-azure-identity - Azure Identity for managed identity scenarios
  • @azure/msal-browser - MSAL Browser library (peer dependency)
  • @azure/msal-react - React integration for MSAL

Examples

See complete examples in:

  • samples/spa-fluentui-v9 - Full React SPA with Fluent UI
  • samples/node-cli-devicecode - CLI authentication (uses msal-node)

License

GNU AGPL v3.0

Support