/ src / auth / msalConfig.ts
msalConfig.ts
 1  // Copyright (c) 2026 VPL Solutions. All rights reserved.
 2  // Licensed under the MIT License. See LICENSE for details.
 3  
 4  import { PublicClientApplication, type Configuration, LogLevel } from '@azure/msal-browser';
 5  import { config } from '../config';
 6  
 7  const msalConfig: Configuration = {
 8    auth: {
 9      clientId: config.azure.clientId,
10      authority: config.azure.tenantId
11        ? `https://login.microsoftonline.com/${config.azure.tenantId}`
12        : 'https://login.microsoftonline.com/common',
13      redirectUri: config.azure.redirectUri,
14      postLogoutRedirectUri: config.azure.redirectUri,
15    },
16    cache: {
17      cacheLocation: 'localStorage',
18    },
19    system: {
20      loggerOptions: {
21        logLevel: LogLevel.Verbose,
22        loggerCallback: (_level, message, containsPii) => {
23          if (containsPii) return;
24          console.log(`[MSAL] ${message}`);
25        },
26      },
27    },
28  };
29  
30  // Always include openid/profile/email so idToken is present in acquireTokenSilent
31  // responses. Without openid, MSAL omits idToken from the response and
32  // response.idToken is null — causing Bearer null on all API calls.
33  export const loginRequest = {
34    scopes: config.azure.apiScope
35      ? [config.azure.apiScope, 'openid', 'profile', 'email']
36      : ['openid', 'profile', 'email'],
37  };
38  
39  // Singleton — only instantiated when auth is enabled
40  let msalInstance: PublicClientApplication | null = null;
41  
42  export function getMsalInstance(): PublicClientApplication {
43    if (!msalInstance) {
44      msalInstance = new PublicClientApplication(msalConfig);
45    }
46    return msalInstance;
47  }