Search code examples
angularangular-materialangular-guards

How can I protect a URL based on the input of an Angular Material Dialog using a Guard?


Goal:

I have a specific URL protected using a Guard. When a user attempts to access that URL I have an Angular 4 material dialog open. Based on the dialog input, I want to authorize or not authorize the user.

Issue:

In the Guard I subscribe to the dialog. On close I receive the dialog input. When the user attempts to access the URL, canActivate is automatically evaluated to false without waiting for user input. In other words, the modal is subscribed to, but false is returned immediately because the function does not wait for the dialog to close.

Question:

How can I authorize or not authorize a user to a URL based on user input?

Guard:

    @Injectable()
    export class VerificationGuard implements CanActivate {

      pwd: string;
      auth: boolean;

      constructor(private dialog: MatDialog) {
      }

      public canActivate() {
        const dialog = this.dialog.open(VerificationDialogComponent);
        dialog.afterClosed()
          .subscribe(val => {
            if (val) {
              this.pwd = val;
              return true;
            }
          });
        return false;
      }
    }

Dialog:

    import { Component, OnInit } from '@angular/core';
    import { MatDialogRef } from '@angular/material';

    @Component({
      selector: 'app-verification-dialog',
      templateUrl: './verification-dialog.component.html',
      styleUrls: ['./verification-dialog.component.scss']
    })
    export class VerificationDialogComponent implements OnInit {

      pwd: string;

      constructor(public dialogRef: MatDialogRef<VerificationDialogComponent>) { }

      ngOnInit() {
      }

      /**
       * Close dialog and pass back data.
       */
      confirmSelection() {
        this.dialogRef.close(this.pwd);
      }
    }

Solution

  • Instead of opening this modal from VerificationGuard, consider using a service to store a flag.

    @Injectable()
    export class AuthService {
      isLoggedIn = false;
    }
    

    The service doesn't log you in, but it has a flag to tell you whether the user is authenticated.

    Call it from your Guard:

    @Injectable()
    export class VerificationGuard implements CanActivate {
    
      constructor(private authService: AuthService) {}
    
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        return this.authService.isLoggedIn)
      }
    }
    

    Relocate your modal logic to where your router navigation event is being emitted, and have it do the following on submitting the credentials:

    1. Set AuthService.isLoggedIn to true.
    2. Emit the router navigation event.
    3. Set AuthService.isLoggedIn to false from the guard.

    AuthService.isLoggedIn should be reset to false and canActivate() should return true.

    See https://angular.io/guide/router#canactivatechild-guarding-child-routes under "Teach AuthGuard to authenticate" for a similar example.