DEV Community

Discussion on: AWS Amplify Auth & Angular RxJS Simple State Management

 
beavearony profile image
Michael Gustmann

You can just do

canActivate() {
    return this.authService.isLoggedIn$;
}

Do not subscribe here! In your case it will resolve to false immediately and never actually care about the subscribe callback. Also it will create a leak, if you do not unsubscribe. When returning an Observable, Angular will do it for you!

What I actually do is talk to the Amplify Auth class directly:

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(
    private router: Router,
    @Inject(PLATFORM_ID) private platformId
  ) {}

  canActivate() {
    if (isPlatformBrowser(this.platformId)) {
      return Auth.currentAuthenticatedUser()
        .then(_user => {
          return true;
        })
        .catch(_err => {
          this.router.navigate(['/auth']);
          return false;
        });
    } else {
      return true;
    }
  }
}

I have to check for the Browser platform, because Amplify is not SSR compatible. But this will directly look at the saved value in localStorage.

Thread Thread
 
bhill9270 profile image
Ben • Edited

Thanks for the suggestions!!

I've tried both, and still get 'not authenticated' on the first load/login of the app. The only thing I've been able to get to work is adding a timeout in the auth service to give it time to populate the localstorage. Here is a related github issue: github.com/aws-amplify/amplify-js/...

When the ADFS system redirects the user to the site with a auth code (instead of token) Amplify has to make multiple calls out to Cognito, and the canActivate is not waiting for the calls Amplify is making to return.

I'm going to try adding a loop in the auth service that checks for the localstorage before performing any .next actions. I'll let you know how it turns out and post my solution!

Edit: This seems to do the trick:

     private getAuthToken(i: number) {
      setTimeout(() => {
        const ampToken = localStorage.getItem('amplify-signin-with-hostedUI');
         if (!ampToken && i < 500) { //stopping the loop eventually if the user isn't logged in
           i++
           this.getAuthToken(i);
        }
     return this.checkAuthorization();
   }, 20); //Loops every 20 ms
 }

 private checkAuthorization() {
  Auth.currentAuthenticatedUser().then(
  (user: any) => { 
    this.setUser(user) },
  _err => console.log(_err)
  );
 }

Would love to hear any other ideas on waiting on the tokens to populate.

Thread Thread
 
beavearony profile image
Michael Gustmann

One note about my previous comment. If you return an observable in a guard, you need to make sure it is completed. To do that, you can just add take(1):

return this.auth.isLoggedIn$.pipe(take(1));

What you can think about as well is to wait for rendering the app until the Auth.currentAuthenticatedUser() resolves.

Wrap the AppComponent's HTML with a

<ng-container *ngIf="authChecked">
   ... app-component.html stuff
</ng-container>
export class AppComponent {
  authChecked = false;
  constructor() {
    Auth.currentAuthenticatedUser().then(
      _user => (this.authChecked = true),
      _err => (this.authChecked = true)
    );
  }
}