Reputation: 3298
I m using MSAL 1.3 to authenticate Azure B2C users in my Angular 8 app which has .net core back end API. It all works great except when a user is not logged in, we get an error when trying to call an unprotected end point in my webapi to register new users. The call is made in a serivce class (service.ts) and the error I get say's
MSAL Logging: Thu, 18 Jun 2020 16:21:19 GMT:2335b876-d867-4b1f-9d3f-d285caa0ee04-1.3.0-Error Error when acquiring token for scopes: https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read ClientAuthError: User login is required. For silent calls, request must contain either sid or login_hint mycomponent.component.ts:132 ClientAuthError: User login is required. For silent calls, request must contain either sid or login_hint
The API its trying to reach has been added as unprotectedResources
and MSAL should not try to get token silently and check if the User is logged in.
My b2c config looks like below
import { Configuration } from 'msal';
import { MsalAngularConfiguration } from '@azure/msal-angular';
// this checks if the app is running on IE
export const isIE = window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1;
export const b2cPolicies = {
names: {
signUpSignIn: "b2c_1_susi",
resetPassword: "b2c_1_reset",
},
authorities: {
signUpSignIn: {
authority: "https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/b2c_1_susi"
},
resetPassword: {
authority: "https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/b2c_1_reset"
}
}
}
export const apiConfig: {b2cScopes: string[], webApi: string} = {
b2cScopes: ['https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read'],
webApi: 'https://fabrikamb2chello.azurewebsites.net/hello'
};
export const msalConfig: Configuration = {
auth: {
clientId: "e760cab2-b9a1-4c0d-86fb-ff7084abd902",
authority: b2cPolicies.authorities.signUpSignIn.authority,
redirectUri: "http://localhost:6420/",
postLogoutRedirectUri: "http://localhost:6420/",
navigateToLoginRequestUrl: true,
validateAuthority: false,
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: isIE, // Set this to "true" to save cache in cookies to address trusted zones limitations in IE
},
}
export const loginRequest: {scopes: string[]} = {
scopes: ['openid', 'profile'],
};
// Scopes you enter will be used for the access token request for your web API
export const tokenRequest: {scopes: string[]} = {
scopes: apiConfig.b2cScopes // i.e. [https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read]
};
export const protectedResourceMap: [string, string[]][] = [
[apiConfig.webApi, apiConfig.b2cScopes] // i.e. [https://fabrikamb2chello.azurewebsites.net/hello, ['https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read']]
];
export const msalAngularConfig: MsalAngularConfiguration = {
popUp: !isIE,
consentScopes: [
...loginRequest.scopes,
...tokenRequest.scopes,
],
unprotectedResources: ["https://fabrikamb2chello.azurewebsites.net/api/register"], // API calls to these coordinates will NOT activate MSALGuard
protectedResourceMap, // API calls to these coordinates will activate MSALGuard
extraQueryParameters: {}
}
My app module looks like below
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatListModule } from '@angular/material/list';
import { MatToolbarModule } from '@angular/material/toolbar';
import { Configuration } from 'msal';
import {
MsalModule,
MsalInterceptor,
MSAL_CONFIG,
MSAL_CONFIG_ANGULAR,
MsalService,
MsalAngularConfiguration
} from '@azure/msal-angular';
import { msalConfig, msalAngularConfig } from './app-config';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
function MSALConfigFactory(): Configuration {
return msalConfig;
}
function MSALAngularConfigFactory(): MsalAngularConfiguration {
return msalAngularConfig;
}
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProfileComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
HttpClientModule,
MatToolbarModule,
MatButtonModule,
MatListModule,
AppRoutingModule,
MatCardModule,
MsalModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_CONFIG,
useFactory: MSALConfigFactory
},
{
provide: MSAL_CONFIG_ANGULAR,
useFactory: MSALAngularConfigFactory
},
MsalService
],
bootstrap: [AppComponent]
})
export class AppModule { }
Any ideas?
Upvotes: 7
Views: 12982
Reputation: 545
When you are configuring MSAL in the framework object you can use unprotectedResources to add a list of strings that will be the urls of the unprotected endpoints.
{
auth: {
clientId: environment.clientId,
authority: environment.authorityUrl,
validateAuthority: false,
postLogoutRedirectUri: postLogoutUri
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
},
framework: {
unprotectedResources: [
`====> YOUR_ENDPOINT_UTL <====`,
'https://www.microsoft.com/en-us/',
'./assets/i18n'
],
protectedResourceMap: new Map(protectedResourceMap)
}
};
It's not working for you because you're configuring it in the wrong place (or things have changed, I'm working with msal 1.3.0). You are configuring the unprotected URLs in the MSAL_CONFIG_ANGULAR configuration and not in MSAL_CONFIG.
export function createTranslateRootLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
// Changelog service
export function app_Init(changelogHttpService: ChangelogHttpService) {
return () => changelogHttpService.initializeApp();
}
// MSAL params
export const protectedResourceMap: [string, string[]][] = [
['https://login.microsoftonline.com/Sorry_I_can't_show_this/', ['user.read']]
];
export function postLogoutUri() {
return window.location.protocol + '//' + window.location.host + '/home';
}
export function getConfig() {
return {
auth: {
clientId: environment.clientId,
authority: environment.authorityUrl,
validateAuthority: false,
postLogoutRedirectUri: postLogoutUri
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
},
framework: {
unprotectedResources: [
`${environment.base_url}/mip/company/ <== My new endpoint`,
'https://www.microsoft.com/en-us/',
'./assets/i18n'
],
protectedResourceMap: new Map(protectedResourceMap)
}
};
}
export function getConfigAngular() {
return {
popUp: false,
consentScopes: ['user.read', 'openid', 'profile', `api://${environment.clientId}/access_as_user`],
extraQueryParameters: {}
};
}
// Init
export function initApp(app: InitService) {
return () => app.init();
}
@ NgModule({
declarations: [AppComponent],
imports: [
HttpClientModule,
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
MaterialModule,
// Translate module configure
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateRootLoader,
deps: [HttpClient]
}
}),
// MSAL configure
MsalModule,
// Notifier configure
NotifierModule.withConfig(customNotifierOptions)
],
entryComponents: [],
providers: [
MsalService,
BroadcastService, {
provide: MSAL_CONFIG,
useValue: getConfig()
}, {
provide: MSAL_CONFIG_ANGULAR,
useValue: getConfigAngular()
}, {
provide: HTTP_INTERCEPTORS,
useClass: BypassInterceptor,
multi: true
}, {
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
}, {
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptor,
multi: true
}, {
provide: HTTP_INTERCEPTORS,
useClass: HttpSuccessInterceptor,
multi: true
}, {
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [InitService],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
Upvotes: 0
Reputation: 133
According to their documentation As of @azure/[email protected], protectedResourceMap supports wildcard patterns that are supported by minimatch, and unprotectedResources is deprecated and ignored.
Instead put protectedResourceMap: null
should work
Reference: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2217
Upvotes: 2
Reputation: 3298
It works when the unprotected resources array contains list of all the end points including individual routes e.g.
Just adding the following Url does not work
https://some-webapi.azurewebsites.net/api/Home/
However the following does work
https://some-webapi.azurewebsites.net/api/Home/HelloGet
https://some-webapi.azurewebsites.net/api/Home/HelloPost
Upvotes: 0