rumi
rumi

Reputation: 3298

Error in using MSAL library to authenticate users in azure b2c and unprotected resources

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

Answers (3)

IvanAllue
IvanAllue

Reputation: 545

MSAL_CONFIG configuration object:

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)
    }
};

Why doesn't it work for you?

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.

Final score:

I share the final result of my module to show my configuration of MSAL_CONFIG and MSAL_ANGULAR_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

Fadi Barakat
Fadi Barakat

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

rumi
rumi

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

Related Questions