published on

Angularfire V7 Auth Guard

As of now, the AngularFire v7(“Beta”) does not have an implementation for an auth guard(which I really need), so I went ahead, copied the original auth guard code (from v6), and refactored it to make it compatible with v7.

This code works on Angular 12. I haven’t tested it for older versions, but it should work.

Here’s the code:

import { Injectable } from '@angular/core';
import { Auth } from '@angular/fire';
import { authState } from '@angular/fire/auth';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  Router,
  RouterStateSnapshot,
  UrlTree,
} from '@angular/router';
import { User } from '@firebase/auth';
import { Observable, of, pipe, UnaryFunction } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';

export type AuthPipeGenerator = (
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => AuthPipe;
export type AuthPipe = UnaryFunction<
  Observable<User | null>,
  Observable<boolean | string | any[]>
>;

export const loggedIn: AuthPipe = map((user) => !!user);

/**
 * Auth guard based on AngularFire v6 AngularFireAuthGuard.
 * Check out the v6 implementation here: https://git.io/JZOcy
 */
@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private auth: Auth, private router: Router) {}
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    const authPipeFactory =
      (route.data.authGuardPipe as AuthPipeGenerator) || (() => loggedIn);
    return authState(this.auth).pipe(
      take(1),
      authPipeFactory(route, state),
      map((can) => {
        if (typeof can === 'boolean') {
          return can;
        } else if (Array.isArray(can)) {
          return this.router.createUrlTree(can);
        } else {
          return this.router.parseUrl(can);
        }
      })
    );
  }
}

export const canActivate = (pipe: AuthPipeGenerator) => ({
  canActivate: [AuthGuard],
  data: { authGuardPipe: pipe },
});

export const isNotAnonymous: AuthPipe = map(
  (user) => !!user && !user.isAnonymous
);
export const idTokenResult = switchMap((user: User | null) =>
  user ? user.getIdTokenResult() : of(null)
);
export const emailVerified: AuthPipe = map(
  (user) => !!user && user.emailVerified
);
export const customClaims = pipe(
  idTokenResult,
  map((idTokenResult) => (idTokenResult ? idTokenResult.claims : []))
);
export const hasCustomClaim: (claim: string) => AuthPipe = (claim) =>
  pipe(
    customClaims,
    map((claims) => claims.hasOwnProperty(claim))
  );
export const redirectUnauthorizedTo: (redirect: string | any[]) => AuthPipe = (
  redirect
) =>
  pipe(
    loggedIn,
    map((loggedIn) => loggedIn || redirect)
  );
export const redirectLoggedInTo: (redirect: string | any[]) => AuthPipe = (
  redirect
) =>
  pipe(
    loggedIn,
    map((loggedIn) => (loggedIn && redirect) || true)
  );

Note: If you are an contributor of the Angularfire library, you can steal this code and cram it in the library. Just keep a link to this article ;)