Angular-auth-oidc-client: Guards and making a decision based on 2 observable calls

Created on 9 May 2019  路  11Comments  路  Source: damienbod/angular-auth-oidc-client

My guard is trying to make a decision on activation based on the results of getIsAuthorized() and getUserData(). We need to determine if the authorized user's roles are allowed for the route.

The getIsAuthorized() seems to return in time, but the getUserData() comes back false initially and the activation is false, but then returns with the data. The route was already canceled.

I've tried multiple suggestions without much luck. Is there an example that performs at least 2 observable calls inside a guard that needs the answers from both before returning?

Release 11 investigate

Most helpful comment

I seem to have a similar or at least related issue - in our application, if a user opens a new tab on some given route for example by CTRL-CLICK on a menu item, the securityService.getUserData() call returns an empty string initially. This in turn makes our auth guard reject the route activation. In our case we show an unauthorized dialog if the user data does not contain expected role claims. The securityService.getIsAuthorized() does return true just like eheinz57 observed. I am not really sure why the user data observable returns empty initially, when the .getIsAuthorized() does not return false, meaning the user is logged in?

All 11 comments

One attempt:

return this._securityService.getIsAuthorized().pipe(
            mergeMap(isAuthorized => this._securityService.getUserData().pipe(
                map((userData: any) => {
                    if (isAuthorized && userData && this.isInternalUser(userData) && this.roleRequirementMet(['InternalAdmin'], userData.role)) {
                        return true;
                    }
                    return false;
                })
            )),
            take(1)
        );

Another attempt:

canActivate(): boolean {
        this._securityService.getIsAuthorized().subscribe(
            (isAuthorized: boolean) => {
                this.isAuthorized = isAuthorized;
            });
        this._securityService.getUserData().subscribe(
            (userData: any) => {
                if (userData && userData !== '') {
                    for (let i = 0; i < userData.role.length; i++) {
                        if (userData.role[i] === 'admin') {
                            this.hasUserAdminRole = true;
                        }
                    }
                }
            });
        return this.hasUserAdminRole && this.isAuthorized;
    }

Hey @eheinz57 ,

there are multiple ways to achieve that. The first solution you could try is using combineLatest. Please use the standalone function from combinelatest. You can use it like this:

import { combineLatest } from 'rxjs';

  const observable1$ = this._securityService.getUserData();
    const observable2$ = this._securityService.getIsAuthorized();

    return combineLatest(observable1$, observable2$).pipe(
      map(([obs1result, obs2result]) => {
        // do something with the values
      })
    );

You can also try to use withlatestFrom()

 return this._securityService.getUserData().pipe(
      withLatestFrom(this._securityService.getIsAuthorized()),
      map(([auth1, auth2]) => {
         // do something with the values
      }),
    );

Or you use the good old switchmap() to get the first done, then the second, and then do something with the result.

I just typed that down here in the editor. So see that as pseudo code :)

Thanks

Fabian

I seem to have a similar or at least related issue - in our application, if a user opens a new tab on some given route for example by CTRL-CLICK on a menu item, the securityService.getUserData() call returns an empty string initially. This in turn makes our auth guard reject the route activation. In our case we show an unauthorized dialog if the user data does not contain expected role claims. The securityService.getIsAuthorized() does return true just like eheinz57 observed. I am not really sure why the user data observable returns empty initially, when the .getIsAuthorized() does not return false, meaning the user is logged in?

This sounds strange, we have to investigate this.

Internally both _isAuthorized and _userData are BehaviorSubjects initialised with false and '' respectively.

The initial value for the _userData is what is causing the issue here, I'm not sure BehaviorSubject is the best choice for this use.

When I want to access the oidcSecurityService.getUserData() I've been using this workaround to ensure we've got values instead of the initial value.

this.oidcSecurityService.getUserData()
    // userData is a behaviorSubject
    // we want to skip any initial empty state
    .pipe(
      skipWhile(userData => !userData)
    )

Thanks @michaelgregson , so a simple Subject would be best here, no?

Thanks for the quick attention @FabianGosebrink, I'd agree a Subject would be ideal

Will take a look into this at the weekend :) Thanks for pointing this out. Really appreciate it. Will leave this open until PR is done and close it then. Thanks!

Currently found the time to dig into this. However changing the BehaviorSubject into a Subject is not the only change which has to be done. It will change the API. Will hopefully add a PR later.

This has been refactored and changed in verison 11 which we plan to release in the next few days after testing. See the new docs for Guards

Greetings Damien

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jonesie picture Jonesie  路  4Comments

xaviergxf picture xaviergxf  路  3Comments

sdev95 picture sdev95  路  3Comments

Roman1991 picture Roman1991  路  4Comments

yelhouti picture yelhouti  路  4Comments