Core Library
MSAL.js (@azure/msal-browser)
Core Library Version
^5.1.0
Wrapper Library
MSAL Angular (@azure/msal-angular)
Wrapper Library Version
^5.0.0
Public or Confidential Client?
Public
Description
We are experiencing inconsistent behavior with MsalInterceptor in @azure/msal-angular when using protectedResourceMap.
The interceptor does not reliably attach tokens when using a base API URL, but works when using specific endpoint paths.
This makes it impractical to configure applications with multiple endpoints.
Error Message
The MSAL Angular interceptor does not consistently attach an access token when using a base API URL in protectedResourceMap.
Scenario
When configuring the interceptor with a base path:
protectedResourceMap.set('https://localhost:7203/api/', [
'api://<client-id>/access_as_user',
]);
and making an API call:
https://localhost:7203/api/users/
Observed Behavior
❌ The Authorization header is not attached
❌ No access token is sent with the request
Workaround
When configuring the interceptor with a more specific endpoint path:
protectedResourceMap.set('https://localhost:7203/api/users/', [
'api://<client-id>/access_as_user',
]);
Observed Behavior
✅ The Authorization header is attached
✅ The access token is correctly included in the request
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
MSAL Configuration
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: environment.CLIENT_ID,
authority: environment.AUTHORITY,
redirectUri: '/redirect',
postLogoutRedirectUri: '/login',
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
},
system: {
allowPlatformBroker: false,
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
});
}
Relevant Code Snippets
import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter, withInMemoryScrolling } from '@angular/router';
import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { providePrimeNG } from 'primeng/config';
import { routes } from './app.routes';
import Aura from '@primeuix/themes/aura';
import {
MsalService,
MsalInterceptor,
MsalInterceptorConfiguration,
MSAL_INTERCEPTOR_CONFIG,
MsalGuardConfiguration,
MSAL_INSTANCE,
MSAL_GUARD_CONFIG,
MsalGuard,
MsalBroadcastService,
} from '@azure/msal-angular';
import {
PublicClientApplication,
InteractionType,
BrowserCacheLocation,
IPublicClientApplication,
LogLevel,
} from '@azure/msal-browser';
import { environment } from '../environments/environment';
export function loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
}
/* 🔹 MSAL instance */
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: environment.CLIENT_ID,
authority: environment.AUTHORITY,
redirectUri: '/redirect',
postLogoutRedirectUri: '/login',
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
},
system: {
allowPlatformBroker: false,
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://localhost:7203/api/', [
'api://<client-id>/access_as_user',
]);
protectedResourceMap.set(environment.GRAPH_API, ['user.read', 'profile']);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ['openid', 'profile', 'User.Read'],
},
loginFailedRoute: '/login',
};
}
export const appConfig: ApplicationConfig = {
providers: [
/* 🔹 HttpClient required by MSAL */
provideHttpClient(withInterceptorsFromDi()),
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory,
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
MsalService,
MsalGuard,
MsalBroadcastService,
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(
routes,
withInMemoryScrolling({
scrollPositionRestoration: 'top',
anchorScrolling: 'enabled',
}),
),
provideAnimations(),
providePrimeNG({
theme: {
preset: Aura,
},
}),
],
};
Reproduction Steps
- Configure MSAL in app.config.ts with interceptor:
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://localhost:7203/api/', [
'api://<client-id>/access_as_user',
]);
- Register interceptor:
provideHttpClient(withInterceptorsFromDi()),
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory,
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
-
Make an API call using Angular HttpClient:
this.http.get('https://localhost:7203/api/users').subscribe();
-
Open browser DevTools → Network tab → inspect the request
Observe
❌ Authorization header is NOT attached
❌ No token is added to the request
-
Update interceptor configuration to a more specific path:
protectedResourceMap.set('https://localhost:7203/api/users/', [
'api://<client-id>/access_as_user',
]);
- Repeat the same API call:
this.http.get('https://localhost:7203/api/studies').subscribe();
7.Observe
✅ Authorization header is now attached
✅ Token is added to the request
- Repeat similar test with Microsoft Graph:
protectedResourceMap.set('https://graph.microsoft.com/v1.0', ['User.Read']);
Call:
this.http.get('https://graph.microsoft.com/v1.0/me').subscribe();
this.http.get('https://graph.microsoft.com/v1.0/me/photo/$value').subscribe();
Observe
/me may work
/me/photo/$value returns 401 (token not attached)
- Add exact endpoint mapping:
protectedResourceMap.set(
'https://graph.microsoft.com/v1.0/me/photo/$value',
['User.Read']
);
Observe
✅ Token is attached
✅ Request succeeds
Conclusion from Reproduction:
Token is only attached for specific or exact paths
Expected Behavior
Base URL mapping should work:
protectedResourceMap.set('https://localhost:7203/api/', [...]);
👉 Should attach token for ALL:
/api/studies
/api/users
/api/orders/123
Actual Behavior:
Token is only attached when:
Full endpoint path is used
👉 This forces developers to define multiple mappings per endpoint, which is not scalable.
Identity Provider
Entra ID (formerly Azure AD) / MSA
Browsers Affected (Select all that apply)
Chrome, Edge
Regression
v3
Core Library
MSAL.js (@azure/msal-browser)
Core Library Version
^5.1.0
Wrapper Library
MSAL Angular (@azure/msal-angular)
Wrapper Library Version
^5.0.0
Public or Confidential Client?
Public
Description
We are experiencing inconsistent behavior with MsalInterceptor in @azure/msal-angular when using protectedResourceMap.
The interceptor does not reliably attach tokens when using a base API URL, but works when using specific endpoint paths.
This makes it impractical to configure applications with multiple endpoints.
Error Message
The MSAL Angular interceptor does not consistently attach an access token when using a base API URL in protectedResourceMap.
Scenario
When configuring the interceptor with a base path:
and making an API call:
https://localhost:7203/api/users/Observed Behavior
❌ The Authorization header is not attached
❌ No access token is sent with the request
Workaround
When configuring the interceptor with a more specific endpoint path:
Observed Behavior
✅ The Authorization header is attached
✅ The access token is correctly included in the request
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
MSAL Configuration
Relevant Code Snippets
Reproduction Steps
Make an API call using Angular HttpClient:
this.http.get('https://localhost:7203/api/users').subscribe();Open browser DevTools → Network tab → inspect the request
Observe
❌ Authorization header is NOT attached
❌ No token is added to the request
Update interceptor configuration to a more specific path:
this.http.get('https://localhost:7203/api/studies').subscribe();7.Observe
✅ Authorization header is now attached
✅ Token is added to the request
protectedResourceMap.set('https://graph.microsoft.com/v1.0', ['User.Read']);Call:
Observe
/me may work
/me/photo/$value returns 401 (token not attached)
Observe
✅ Token is attached
✅ Request succeeds
Conclusion from Reproduction:
Token is only attached for specific or exact paths
Expected Behavior
Base URL mapping should work:
protectedResourceMap.set('https://localhost:7203/api/', [...]);👉 Should attach token for ALL:
/api/studies
/api/users
/api/orders/123
Actual Behavior:
Token is only attached when:
Full endpoint path is used
👉 This forces developers to define multiple mappings per endpoint, which is not scalable.
Identity Provider
Entra ID (formerly Azure AD) / MSA
Browsers Affected (Select all that apply)
Chrome, Edge
Regression
v3