Microsoft-authentication-library-for-js: Routes without Router guard are pushed to unprotectedResources

Created on 21 Jan 2019  路  4Comments  路  Source: AzureAD/microsoft-authentication-library-for-js

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:

Browser:

  • [ ] Chrome version XX
  • [ ] Firefox version XX
  • [ ] IE version XX
  • [ ] Edge version XX
  • [ ] Safari version XX

Library version


Library version: 0.1.2



## Current behavior
Routes NOT protected by router guards are pushed in to the array of unprotectedResources causing 401 when a protected resource url is matched against the route path.
The route path is not the compelte url i.e. "https://example.xyz/banana" but "banana" causing the string "banana" to be pushed to unprotectedResources making ANY resource with the word "banana" in it treated as a unprotectedResource and the interceptor wont attach the bearer token to the request.


## Expected behavior
Partial names of route config should not be pushed in to the unprotectedResources array.
Resources https://example.xyz/api/bananas should not be treated as a unprotected just because there is a route without a guard thats named banana.

## Minimal reproduction of the problem with instructions
Setup MSAL and interceptor
Setup routes protected with route guards
Create one route without a guard that contains a partial match of any of the protected backend API calls
Make a request to API where the is a partial match.



const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'coconuts', loadChildren: './coconuts/coconuts.module#CoconutsModule', canActivate: [RoleGuard], data: { roles: ['FruitAdmin'] } },
  { path: 'banana', loadChildren: './banana/banana.module#BananaModule' }, // this will make the API call to api/bananas/ fail with 401
  { path: 'bananas', loadChildren: './bananas/bananas.module#BananasModule', canActivate: [RoleGuard], data: { roles: ['FruitAdmin'] }, pathMatch: 'full' }
];


// this API endpoints is protected and 
function getAllBananas(): void {
  this.http.get<Banana[]>(this.baseUrl + 'api/bananas/').subscribe(results => {
      console.log(results);
    },
    error => console.error(error));
}

// this API is unprotected by anyone with the specific id
function getSpecificBanana(id: number): void {
  this.http.get<Banana>(this.baseUrl + 'api/banana/' + id).subscribe(results => {
      console.log(results);
    },
    error => console.error(error));
}

Since the banana route is setup in the route config without a guard this will be pushed to the unprotectedResources array in the MSAL.service (line 55)

this.router.events.subscribe(event => {
            for (var i = 0; i < router.config.length; i++) {
                if (!router.config[i].canActivate) {
                    if (this.config && this.config.unprotectedResources) {
                        if (!this.isUnprotectedResource(router.config[i].path) && 
this.isEmpty(router.config[i].path)) {
                            this.config.unprotectedResources.push(router.config[i].path);
                        }
                    }
                }
            }
        });

and map to ANY url containing"banana" (line 220)

isUnprotectedResource(url) {
        if (this.config && this.config.unprotectedResources) {
            for (var i = 0; i < this.config.unprotectedResources.length; i++) {
                if (url.indexOf(this.config.unprotectedResources[i]) > -1) { //Will match anything containing banana
                    return true;
                }
            }
        }
        return false;
    }
msal-angular

Most helpful comment

Another simple workaround if you do not have unprotected resources: you can simply leave config.unprotectedResources as undefined. No routes will get added to the list if undefined. This works for the case when you are only protecting API endpoints.

All 4 comments

Is the app you tested with registered on Azure as a single tenant or as a multi-tenant (using the common endpoint)?

@visualjeff It is not using the common endpoint, its targeting our instance of Azure B2C https://login.microsoftonline.com/tfp/xyz.onmicrosoft.com/B2C_1A_yyyxxxzzz/v2

Found a easy workaround for this issue and its to simply create a Guard that returns true directly and assign it to any route that you want anon access.

export class AnonGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return true;
  }
}

and then use it in the route configuration like

{ path: "banana", loadChildren: "./banana/banana.module#BananaModule", canActivate: [AnonGuard], pathMatch: "full" },
  { path: "bananas", loadChildren: "./bananas/bananas.module#BananasModule", canActivate: [RoleGuard], data: { roles: ["BananaAdmin"] }, pathMatch: "full" }

This prevents the router path for the previous unguarded route to be pushed to unprotectedResoures.

Another simple workaround if you do not have unprotected resources: you can simply leave config.unprotectedResources as undefined. No routes will get added to the list if undefined. This works for the case when you are only protecting API endpoints.

@Weschk Thanks for raising this issue. Linking this with #786 -> will add this as one of the use cases to test when the new angular wrapper work starts.

@negoe @DarylThayil FYI.

Was this page helpful?
0 / 5 - 0 ratings