Dataverse On-Behalf-Of Authentication
The `dataverse-auth` package provides on-behalf-of (OBO) authentication capabilities against Microsoft Dataverse environments, specifically designed for NodeJS applications. It facilitates the process of obtaining and securely storing access tokens locally, which can then be utilized by other applications, such as code generation tools (`dataverse-gen`) or API clients (`dataverse-ify`). The library primarily leverages OAuth 2.0 for its authentication flows, handling interactive logins. The current stable version on npm is 1.0.9. Despite this version being last published over five years ago, the author maintains related Dataverse ecosystem packages, suggesting a maintenance status for `1.x` while newer approaches or specific platform requirements (like for Apple Silicon, which mentions `dataverse-auth@2`) may exist outside of the npm `1.x` release line. Its key differentiator is simplifying the token management for Dataverse within Node.js applications, abstracting away some complexities of Microsoft Entra ID (formerly Azure AD) authentication flows.
Common errors
-
Authentication failed: InteractionRequiredAuthError: invalid_grant: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000007-0000-0000-c000-000000000000'.
cause Your Microsoft Entra ID tenant or Dataverse environment requires Multi-factor Authentication (MFA), which the current authentication flow (or its configuration) is not satisfying.fixReview Microsoft Entra ID Conditional Access policies and Security Defaults. If interactive login is intended, ensure the application registration supports interactive flows and users complete MFA prompts. For non-interactive scenarios (e.g., backend services), switch to service principal (client credentials flow) or managed identity authentication instead of user-based flows. -
Access Denied. Not enough privilege to access the Microsoft Dynamics CRM object or perform the requested operation.
cause The authenticated user or service principal lacks the necessary security roles or privileges in Dataverse to perform the requested operation.fixVerify that the user account or application user (for service principals) used for authentication has been assigned the appropriate security roles and privileges within the Dataverse environment. This often requires an administrator to grant specific read/write/create/delete permissions on relevant tables. -
Error: unable to get local issuer certificate (for self-signed certs or corporate proxies)
cause Node.js cannot verify the SSL/TLS certificate of the Dataverse endpoint or an intermediate proxy, common in corporate environments with deep packet inspection or self-signed certificates.fixIf using a corporate proxy or self-signed certificates, configure Node.js to trust the necessary CA certificates by setting the `NODE_EXTRA_CA_CERTS` environment variable to the path of your CA certificate bundle. For example: `export NODE_EXTRA_CA_CERTS=/path/to/your/ca-certs.pem`.
Warnings
- breaking The `dataverse-auth` package version 1.x does not officially support MacOS with Apple Silicon (M1/M2 chips). Users on these platforms may need to use `npx dataverse-auth@2` if a `v2` version of the CLI tool or an updated library is available, or encounter compatibility issues. The version 2 mention on GitHub appears to be for CLI usage and does not correspond to a `dataverse-auth@2` npm package.
- gotcha Multi-factor Authentication (MFA) can cause authentication failures with an `InteractionRequiredAuthError: invalid_grant: AADSTS50076` if the Dataverse environment or Microsoft Entra ID (Azure AD) tenant has conditional access policies or security defaults enabled that require MFA. This is particularly problematic if device-code flow is disabled or not properly handled, as the library's interactive prompt may not satisfy all MFA requirements.
- deprecated Microsoft has deprecated WS-Trust (Office365) authentication for Dataverse. While `dataverse-auth` uses OAuth 2.0, reliance on legacy authentication types or incorrect configuration can still lead to issues. Microsoft strongly recommends using modern OAuth 2.0 flows with MSAL.
- gotcha The package stores tokens locally for reuse. While `keytar` is used for secure storage, improper handling of the environment where these tokens are stored (e.g., insecure file system permissions, lack of disk encryption) can expose sensitive access tokens.
- deprecated Starting October 2024, support for multi-tenant applications without a service principal in the Microsoft Entra ID tenant is being deprecated for Dataverse. This could impact on-behalf-of flows for applications operating across multiple tenants without explicit service principal registration in each target tenant.
Install
-
npm install dataverse-auth -
yarn add dataverse-auth -
pnpm add dataverse-auth
Imports
- auth
import { auth } from 'dataverse-auth';import auth from 'dataverse-auth';
- AuthType
import type { AuthType } from 'dataverse-auth'; - DeviceCode
import type { DeviceCode } from 'dataverse-auth';
Quickstart
import auth from 'dataverse-auth';
import { TokenResponse } from '@azure/msal-node';
const DATAVERSE_URL = process.env.DATAVERSE_URL ?? 'https://yourorg.crm.dynamics.com';
const TENANT_ID = process.env.TENANT_ID;
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET; // Required for confidential client flows
async function authenticateAndGetToken(): Promise<TokenResponse | undefined> {
try {
console.log(`Attempting to authenticate against Dataverse: ${DATAVERSE_URL}`);
// This function can trigger an interactive browser login if needed,
// or use cached credentials if available.
// For automation, consider 'AuthType.ClientSecret' with CLIENT_ID/CLIENT_SECRET
// or 'AuthType.DeviceCode' with a callback for user interaction.
const tokenResponse = await auth(
DATAVERSE_URL,
TENANT_ID, // Optional: specify tenant ID
CLIENT_SECRET ? 'ClientSecret' : undefined, // Example: use ClientSecret if provided
CLIENT_ID,
CLIENT_SECRET,
// For 'DeviceCode' flow, you might provide a callback:
// undefined, undefined, (deviceCode) => {
// console.log(`Open ${deviceCode.verificationUri} and enter code ${deviceCode.userCode}`);
// }
);
if (tokenResponse) {
console.log('Authentication successful!');
console.log('Access Token:', tokenResponse.accessToken.substring(0, 30) + '...');
console.log('Expires On:', new Date(tokenResponse.expiresOn! * 1000));
return tokenResponse;
} else {
console.log('Authentication flow completed, but no token response was returned.');
return undefined;
}
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
}
authenticateAndGetToken()
.then(token => {
if (token) {
console.log('\nReady to use the token for Dataverse API calls.');
// Example: Use the token with another Dataverse client library
// const dataverseClient = new DataverseClient(token.accessToken, DATAVERSE_URL);
}
})
.catch(err => {
console.error('An unhandled error occurred:', err);
process.exit(1);
});