Reputation: 646
When working on a local copy of our Angular application we need to access the api on the dev server. However since upgrading to using msal-angular this does not work anymore, it returns 401 and goes into a redirection loop.
I'm not sure if there is a configuration we are missing to allow this to work. It worked fine when we were using adal but we need V2 tokens on the api now.
export const protectedResourceMap: [string, string[]][] = [
['https://graph.microsoft.com/v1.0/me', ['user.read']]
];
...
imports: [
MsalModule.forRoot({
clientID: 'Azure-App-Id',
authority: 'https://login.microsoftonline.com/Azure-Tenant-Id',
validateAuthority: true,
redirectUri: window.location.origin,
navigateToLoginRequestUrl: false,
cacheLocation: 'localStorage',
popUp: false,
protectedResourceMap: protectedResourceMap
})
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true }
],
...
The above is my configuration in app.module.ts.
This works fine when I run my angular application from http://localhost:4200 against my asp.net core web api run from visual studio on https://localhost:5600.
It also runs fine when deployed on the server. However if I change the Angular app in dev environment to use the servers api (http://localhost:4200 -> https://www.api.azurewebsite.net), we always get authentication errors as the Angular app does not send the bearer token like it does on the other two cases.
Hopefully this is enough details.
Thank you.
Upvotes: 1
Views: 3623
Reputation: 774
Yeap, for now I made own implementation of MsalInterceptor.
Vote for wildcard in protectedResourceMap https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1776
This is my custom code block:
// #region own workaround in order not to put every api endpoint url to settings
if (!scopes && req.url.startsWith(this.settingsService.apiUrl)) {
scopes = [this.auth.getCurrentConfiguration().auth.clientId];
}
// #endregion
// If there are no scopes set for this request, do nothing.
if (!scopes) {
return next.handle(req);
}
Upvotes: 0
Reputation: 646
I discovered the issue with with msal-angular's interceptor. I guess it does the right thing and the scopes are empty when going cross-domain.
We created our own interceptor from the msal-angular code to inject the scope if we are in development.
Thank you.
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor, HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/mergeMap';
import { MsalService, BroadcastService } from '@azure/msal-angular';
@Injectable()
export class CustomMsalInterceptor implements HttpInterceptor {
constructor(private auth: MsalService, private broadcastService: BroadcastService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.indexOf('geocode.arcgis.com') >= 0) {
return next.handle(req);
} else {
let scopes = this.auth.getScopesForEndpoint(req.url);
this.auth.verbose('Url: ' + req.url + ' maps to scopes: ' + scopes);
if (scopes === null) {
if (req.url.indexOf('azurewebsites')) {
scopes = ['**** APPLICAITON (CLIENT) ID *****'];
} else {
return next.handle(req);
}
}
const tokenStored = this.auth.getCachedTokenInternal(scopes);
if (tokenStored && tokenStored.token) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${tokenStored.token}`,
}
});
return next.handle(req).do(event => { }, err => {
if (err instanceof HttpErrorResponse && err.status === 401) {
const scopes1 = this.auth.getScopesForEndpoint(req.url);
const tokenStored1 = this.auth.getCachedTokenInternal(scopes1);
if (tokenStored1 && tokenStored1.token) {
this.auth.clearCacheForScope(tokenStored.token);
}
}
});
} else {
return Observable.fromPromise(this.auth.acquireTokenSilent(scopes).then(token => {
const JWT = `Bearer ${token}`;
return req.clone({
setHeaders: {
Authorization: JWT,
},
});
})).mergeMap(req1 => next.handle(req1).do(event => { }, err => {
if (err instanceof HttpErrorResponse && err.status === 401) {
const scopes2 = this.auth.getScopesForEndpoint(req1.url);
const tokenStored2 = this.auth.getCachedTokenInternal(scopes2);
if (tokenStored2 && tokenStored2.token) {
this.auth.clearCacheForScope(tokenStored2.token);
}
}
}));
}
}
}
}
Upvotes: 2