5
votes

I'm trying to make a role-based access for my app in angular, and I need some help because I'm newbie in angular ...

First this is what I have in the route where I establish which roles can access it...

from app-routing.module.ts

{
  path: 'profile',
  component: ComunityComponent,
  canActivate: [AuthGuard],
  data: {
    allowedRoles: ['admin', 'user']
  }
},

Second I use the canActivate function to check if the user can access the route or not. from auth.guard.ts

private hasAccess: boolean;

constructor(private router: Router, private auth: AuthService) {}

canActivate(next: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    const allowedRoles = next.data.allowedRoles;
    if (localStorage.getItem('currentUser')) {
      return this.checkAccess(localStorage.getItem('currentUser'), allowedRoles);
    }

    this.router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
    return false;
  }

Third, I develop the function accessLoginToken, that will find the role of the logged user through an httpclient.get service which returns an array with the roles assigned to the user. Example {success: true, roles:['user']} from auth.service.ts

 accessLoginToken(token: string) {
    const check = '/users/access';
    const httpOptions = {
      headers: new HttpHeaders({
        'Authorization': 'Bearer ' + token,
      })
    };
    return this.http.get<any>('/api' + check, httpOptions).pipe(
      catchError(err => this.handleError('accessLoginToken', err))
    );
  }

Fourth in the function checkAccess, I compare it with those of the route and if they match I allow the access otherwise not. from auth.guard.ts

private checkAccess(token: string, allowedRoles: string[]): boolean {
    this.hasAccess = false;
    this.auth.accessLoginToken(token).subscribe(data => {
      if (data.success) {
        data.roles.forEach(rol => {
          if (allowedRoles.findIndex(rols => rols === rol) >= 0) {
            this.hasAccess = true;
          }
        });
      }
    }, (error) => {
      console.log(error);
    });
    console.log(this.hasAccess);
    return this.hasAccess;
  }

The main problem is that I can not get the httpclient subscribe to change the value of the hasAccess variable to true to allow access. Even when the code this.hasAccess = true; is executed the function returns false

By the way I do not like the idea of ​​saving the role in a session variable like the access_token so I'm trying to keep it in the database...

any help on the subject will be appreciated .. thanks

2

2 Answers

3
votes

You're correct, the checkAccess function finishes before the async api call finishes, so false is always returned. Instead you should pass the observable to the canActivate method.

private checkAccess(token: string, allowedRoles: string[]): boolean {
    this.hasAccess = false;
    return this.auth.accessLoginToken(token).pipe(
        map(data => {
            if (data.success) {
                data.roles.forEach(rol => {
                    if (allowedRoles.findIndex(rols => rols === rol) >= 0) {
                        this.hasAccess = true;
                    }
                });
            }
            return this.hasAccess;
        })
    )
}

Because an observable is returned to the canActivate method, the guard will subscribe to the observable and wait for a true or false result.

0
votes

Your problem lies in the fact that you return this.hasAccess while the accessLoginToken is still running. You can return an observable to angular guard.