3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 11

Protecting Routes with Guards in Angular

EXTENDED

Protecting Routes with Guards in Angular

The last feature we are going to add is to prevent unauthenticated users from getting to the home page, and to auto redirect logged in users to the home page. Firebase will remember users automatically, so if we are already logged in we should go directly to the home page.

IMPORTANT: The guards feature I am about to show you is for user experience only. These guards are implemented client side and would not stop a malicious user from getting to any page they like. If you need to restrict certain users from accessing certain things, this needs to happen on the backend — i.e. we should never load the data into the application in the first place. A route guard like the one we are implementing will not stop a malicious user from accessing a route. This is why we have things like our Firestore Security Rules. It doesn’t matter if a malicious user hacks their way past our route guard to gain access to the main /home page without logging in, as no data will load in from Firebase if they haven’t authenticated. They will be on the /home page, but it will be empty.

Redirecting the User

Before we create our guard which will help keep our user where they are supposed to be, we are going to implement some redirects that react to the user’s auth state changing.

The problem in our application right now is that when we try to log in or create an account… nothing happens.

These operations are actually happening successfully, it’s just that our application doesn’t care. What we need to do is have our components react to the authState from Firebase changing by triggering a navigation. We already have a convenient way to do this. Whenever our authState observable emits we set our user state in our AuthService. This results in the following possibilities:

  • If we do not yet know if the user is authenticated, the user() signal will be undefined
  • If the user is unauthenticated the user() signal will be null
  • If the user is authenticated the user() signal will be the User from Firebase

This user() signal updates automatically whenever we change the user’s authState in any way — whether that happens because of a login, logout, create account, or anything else.

We also have a convenient way to trigger running some code, like a navigation, by using the effect Signal API. That means we can just add some effects to each of our components to handle the navigation:

  • The LoginComponent should react by navigating to the home route when the user() signal becomes truthy (i.e. not null or undefined)
  • The RegisterComponent should do the same
  • The HomeComponent should react by navigating to the auth/login route when the user() signal becomes null

See if you can implement this before continuing.

Add the following effect to the LoginComponent:

  private router = inject(Router);

  constructor() {
    effect(() => {
      if (this.authService.user()) {
        this.router.navigate(['home']);
      }
    });
  }

Add the following effect to the RegisterComponent:

  private router = inject(Router);

  constructor() {
    effect(() => {
      if (this.authService.user()) {
        this.router.navigate(['home']);
      }
    });
  }

Add the following effect to the HomeComponent:

  private router = inject(Router);

  constructor() {
    effect(() => {
      if (!this.authService.user()) {
        this.router.navigate(['auth', 'login']);
      }
    });
  }

Now if you try to use the application it should actually appear to work correctly (although it will still look ugly). However, if you happen to have logged in before you will automatically be taken to the home route. This is good, but we don’t actually have a way to trigger a log out, so you will be stuck there.

Modify the template of the HomeComponent to reflect the following:

    <div class="container">
      <mat-toolbar color="primary">
        <span class="spacer"></span>
        <button mat-icon-button (click)="authService.logout()">
          <mat-icon>logout</mat-icon>
        </button>
      </mat-toolbar>

      <app-message-list [messages]="messageService.messages()" />
      <app-message-input (send)="messageService.add$.next($event)" />
    </div>

NOTE: You will need to add the appropriate imports from Angular Material to the imports for the component:

  imports: [
    MessageListComponent,
    MessageInputComponent,
    MatIconModule,
    MatButtonModule,
    MatToolbarModule,
  ],

Handling Re-authentication

EXTENDED
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).