Role-Based Authentication

The system ensures only authorized users can access specific routes based on their assigned roles.

  1. The main routing file defines route paths and lazy-loaded modules for various components based on the user's role.

src/app/app-routing.module.ts
...
{
    path: '',
    component: AdminLayout,
    canActivateChild: [AuthGuardChild],
    children: [
      {
        path: 'dashboard',
        loadChildren: () => import('./demo/dashboard/dashboard.module').then((m) => m.DashboardModule),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'widget/statistics',
        loadComponent: () => import('./demo/widget/statistics/statistics.component').then((c) => c.StatisticsComponent),
        data: {
          roles: [Role.Admin, Role.User]
        }
      },
    ]
}
...
  1. Child Routing Module: Defines the child routes and applies role-based access.

src/app/demo/dashboard/dashboard-routing.module.ts
const routes: Routes = [
  {
    path: '',
    children: [
      {
        path: 'default',
        loadComponent: () => import('./default/default.component').then((c) => c.DefaultComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'e-commerce',
        loadComponent: () => import('./e-commerce/e-commerce.component').then((c) => c.ECommerceComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'crm',
        loadComponent: () => import('./dash-crm/dash-crm.component').then((c) => c.DashCrmComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'analytics',
        loadComponent: () => import('./dash-analytics/dash-analytics.component').then((c) => c.DashAnalyticsComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'crypto',
        loadComponent: () => import('./dash-crypto/dash-crypto.component').then((c) => c.DashCryptoComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'project',
        loadComponent: () => import('./dash-project/dash-project.component').then((c) => c.DashProjectComponent),
        data: { roles: [Role.Admin, Role.User] }
      },
      {
        path: 'finance',
        loadComponent: () => import('./finance/finance.component').then((c) => c.FinanceComponent),
        data: { roles: [Role.Admin] }
      }
    ]
  }
];
  1. Role Management

src/app/theme/shared/_helpers/role.ts
export enum Role {
  User = 'User',
  Admin = 'Admin'
}
  1. Authentication Guard: This AuthGuardChild ensures that users can only access authorized routes.

src/app/theme/shared/_helpers/auth.guard.ts
import { Injectable, inject } from '@angular/core';
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

import { AuthenticationService } from '../service/authentication.service';
import { User } from './user';

@Injectable({ providedIn: 'root' })
export class AuthGuardChild implements CanActivateChild {
  private router = inject(Router);
  private authenticationService = inject(AuthenticationService);

  /**
   * Determines whether a child route can be activated based on user authentication and authorization.
   *
   * @param route - The activated route snapshot that contains the route configuration and parameters.
   * @param state - The router state snapshot that contains the current router state.
   * @returns A boolean or Observable<boolean> indicating whether the route can be activated. Redirects to an appropriate page if not.
   *
   * If the user is logged in and their role is authorized for the route, returns true.
   * If the user is logged in but not authorized, redirects to the unauthorized page and returns false.
   * If the user is not logged in, redirects to the login page with the return URL and returns false.
   * If user data is being loaded, waits for it to complete before making a decision.
   */
  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
    const currentUser = this.authenticationService.currentUserValue;
    const hasToken = !!this.authenticationService.getToken();

    // If we have a token but no user data, fetch it first
    if (hasToken && !currentUser && !this.authenticationService.isLoading) {
      return this.authenticationService.fetchCurrentUser().pipe(
        map((user) => {
          this.authenticationService.isLogin = true;
          return this.checkAuthorization(route, state, user);
        }),
        catchError(() => {
          // If fetch fails, redirect to login
          this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
          return of(false);
        })
      );
    }

    // If user data is currently loading, wait for it to complete
    if (this.authenticationService.isLoading) {
      // Return an observable that waits for loading to complete
      return new Observable<boolean>((observer) => {
        let attempts = 0;
        const maxAttempts = 50; // 5 seconds max (50 * 100ms)

        const checkInterval = setInterval(() => {
          attempts++;
          if (!this.authenticationService.isLoading || attempts >= maxAttempts) {
            clearInterval(checkInterval);
            const user = this.authenticationService.currentUserValue;
            if (user && this.authenticationService.isLoggedIn()) {
              observer.next(this.checkAuthorization(route, state, user));
            } else {
              this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
              observer.next(false);
            }
            observer.complete();
          }
        }, 100);
      });
    }

    // If we have user data, check authorization
    if (currentUser && this.authenticationService.isLoggedIn()) {
      return this.checkAuthorization(route, state, currentUser);
    }

    // User not logged in, redirect to login page
    this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
    return false;
  }

  /**
   * Checks if the user is authorized for the route based on their role
   */
  private checkAuthorization(route: ActivatedRouteSnapshot, state: RouterStateSnapshot, currentUser: User): boolean {
    const { roles } = route.data;
    if (roles && !roles.includes(currentUser.user.role)) {
      // User not authorized, redirect to unauthorized page
      this.router.navigate(['/unauthorized']);
      return false;
    }
    // User is logged in and authorized for child routes
    return true;
  }
}

Last updated